Serverless express with handlebars

Hello everyone!

It’s good to be part of this community. I’ve been using Netlify for a while now, but today I got stuck on some problems I will detail below. Any help is greatly appreciated.

What I’m trying to do: a serverless express app with handlebars view engine.
What happened?

  1. The redirects seem to be broken. I have this repo, which is a fork of this one. I’ve added some redirect rules in netlify.toml that do not work. The strangest thing is that the same redirect rules work just fine when i run netlify dev from my machine. My netlify instance is https://netlify-express-handlebars.netlify.com. The new route is /foo.

  2. On the “long” route, /.netlify/functions/server/foo, the view engine is not working. From the function logs I get this message: Error: Failed to lookup view "foo" in views directory "/var/task/views". This also works on my local machine, when I run netlify dev. Any ideas?

Have a wonderful day!
Victor

Hi @victorocna, are you zipping your view directory in with the rest of your function for deployment? Note that it works locally because all the files are there, but when you deploy your function you need to make sure all of the files you need are part of the functions deployment.

Hi @futuregerald! Great idea, I wasn’t doing it. Now I’m copying the views directory with copy-webpack-plugin. I’ve also set the express views directory where I figured the views would be copied.

app.set('views', path.join(__dirname, '/.netlify/functions/views'))

After I run netlify-lambda build express -c ./webpack.config.js, the functions directory where the app should be deployed has the following structure:

– views
---- foo.hbs
– server.js

I thought it would be enough, but I’m still not able to access the views.
Am I on the right track or did you mean something else by zipping the views? I’ve extensively searched for a similar example, but I could not find one.

Hi,

Yes, have you tried zipping the function yourself to make sure all the files are there and deploying that? that way we can eliminate the build function as a possible culprit while debugging the issue.

Hello,
@victorocna have you solved the issue?
I am too trying to deploy an express app with templates (handlebars in my case) via functions, but when running the app with netlify dev I get an Error: Cannot find module 'hbs'.

My code (the relevant part):

const express = require('express');
const serverless = require('serverless-http');
const app = express();
app.set('views', './views');
app.set('view engine', 'hbs');
const routes = require('./routes');
app.use('/', routes);
module.exports.handler = serverless(app);

Of course the hbs module is correctly installed (if I run the app normal node http server everything works as expected).
My build process copies the views folder inside the functions one, so the final file structure is

functions
├── express.js
├── routes.js
├── views/
└── public/

If I create an endpoint without accessing a template the code works.

Anybody has any suggestion?

Curious where you are specifying your dependency for hbs. Do you have that package in your project’s main package.json file or in the same folder your function is in? If you dependencies are not in your main package.json, then you will need to handle running the npm install for your other package.json files yourself. Hopefully that helps point you in the right direction.

I had hbs loaded in the main package.json.
However I ended up using my own minimalistic template using plain JS template literal, since it seems clear Netlify functions can’t really load/find non-JS files.
Maybe that’s obvious to other people, but the documentation is in general lacking of specifying what is the expected file structure in the production environment.
Also I believe the result was different when running the netlify dev command, so I did not find that really helpful.

So, one of the things I’ve recently tested was accessing an arbitrary file in a deployed function.

First, you’ll need to zip your function yourself to allow you be sure those non-imported files are included in the function zip (example).

The next thing to note is that your lambda function is not executed inside your function’s folder. The execution path is store in the env var LAMBDA_TASK_ROOT (this is set by AWS directly). However, you function folder is under LAMBDA_TASK_ROOT/{function_name}. If your function name is here_is_my_function, the path will be LAMBDA_TASK_ROOT/here_is_my_function. You’ll need to make sure that the paths in your function reflect that (example).

If you see that the behavior is a bit different, feel free to file an issue here: Issues · netlify/cli · GitHub. Doing so would be greatly appreciated!

In any case, I hope that helps clarify how AWS executes lambda functions (which is what we deploy our functions to).

Thanks @Dennis, this is really useful info, I wish the documentation was as clear as you have been.
However, when you say “you’ll need to zip your function” you mean basically copy (not compressing) the files, right? I can do that with webpack for example, not necessarily using zip, can’t I?

And btw, do you have any idea why express would not find hbs among the installed npm modules when using app.set('view engine', 'hbs');? Is the set method maybe referencing the engine module not from the default node_modules folder?

When I mention ‘zip your function’, I do mean that you use zip to create a real zip file containing your function folder.

As far as the set method in express, I’m not sure but I think it does its own look up in your node_modules folder and doesn’t rely on any imports you define. Like I said, manually zipping your function folder with all the necessary files (node_modules, etc.) would probably be the way.

Why is zipping the content necessary, and why just copying the files would not work?
I don’t think I ever found any mention of this practice in the documentation and to me it just seems counterintuitive.

If you copy your files over, our buildbot will use zip-it-and-ship-it to zip your function up. If you don’t import or require the file, it will not be included in the function deploy. This is described here: Get started with Netlify CLI | Netlify Docs. If you want to include artibrary files that you do not import in your function, then you will need to zip your function yourself. Our buildbot will then know to just go ahead and deploy the zip file without further processing.

I think using templating engines with netlify is not a good option, like vercel.
They have clearly mentioned the limitations in their docs that it is not a good option for many reasons.

Read this https://vercel.com/guides/using-express-with-vercel#templating-and-view-engines

Hello everyone!

Long time no see on this post.
Thanks for the info @zippytyro, I was not aware of that.

These days I use some sort of mixed solution, which I believe is suitable for my simple email template needs. The solution goes something like this:

  1. export the email template from a pure js file with placeholders
module.exports = `
  Hello {{ name }}. Thanks for reading my newsletter
`;
  1. import it when needed and compile it in place with handlebars
const loadTemplate = (type) => {
  // load file and return it as a string
};

const compileTemplate = (data, type) => {
  // replace placeholders with handlebars and return the final string

  const template = handlebars.compile(loadTemplate(type));
  return template(data);
}

What do you think about this appoach? I would love to hear your opinion.
All the best,
Victor

1 Like

That’s an interesting approach :+1: . Have you tested it to see if it works? It’s a bit outside the scope of Support at this point but I’d be interested to see how it works out for you. Mostly, I’m trying to see what the advantages are over a static site generator. :thinking:

Hi @victorocna I’m very curious to see if you have a sample repo with your newly revised method. I’m trying to do the same thing and could use a little guidance.

hey there, @jhsu :wave:

Sorry to hear you are encountering obstacles. Can you share a little bit more about what you are trying to do? Additionally, could you share a link to your site with us? This will help us provide the appropriate support to get you on track.

Thanks!

Hi @hillary sorry for the delayed response. I made some progress but am a stuck at trying to load a handlebars template. Here’s my repo and the deployed site.

If you look at ./functions/render/index.js I have the handlebars hardcoded to const data, but I’d like to separate the template into its own file. I tried to do so and received an ENOENT error.

Hey @jhsu

The issue with the template is you are using readFielSync to read a file that does not exist at runtime when it appears this file isn’t in an [included_files] list.

I have a very minimal Markdown renderer demo coelmay/ntl-ssr which is deployed to markdown–ntl-ssr.netlify.app.

1 Like

Ah okay so in your netlify.toml you specify the functions directory and then for specifically the function named posts to include all files in the posts directory that have a markdown extension. Am I interpreting that correctly?