Using Express.js with Vercel

Learn how to use Express.js in a Serverless environment.

Express.js is a popular framework used with Node.js. In this guide, we will cover each step in-depth to deploy an Express.js application to Vercel using Serverless Functions. First, let's understand what we are trying to achieve when deploying Express.js apps to Vercel.

What is Vercel?

Vercel is a cloud platform to deploy frontend applications. It's designed for the frontend experience; build Hybrid frontends with tools like Next.js, develop lightweight event-driven APIs, and deploy to our Global Edge Network.

What is Express.js?

Express.js is a minimal and flexible Node.js web framework. It's a popular choice among developers due to its flexibility, adoption, and trusted ecosystem. Using Express.js implies a server. This server will be running for the lifetime of the application, or until:

  • An unhandled exception (error) is thrown.
  • The server shuts down either manually or by a scheduled task.
  • Software failure outside Node.js or hardware failure.

Traditional APIs are hosted using a running server. As you scale your application, you'll need to consider: cost, flexibility, security, rapid provisioning, and much more. As you can imagine, this is difficult to implement correctly.

Serverless Computing

The serverless approach is quite different. Pieces of backend code run in stateless compute environments that are triggered by events (like requests) and may last for one invocation. It's fully automated and can scale up in milliseconds. Plus, there is no overhead managing and maintaining servers. Developers focus on business logic – functions that return value.

When we deploy a server (Express.js based application) within a Serverless Function, we are executing a full server implementation on each request. This is an anti-pattern, as Serverless Functions should serve one purpose.

Using Express.js with Vercel

We can adopt the serverless approach to build an Express.js app with Vercel. First, create a file index.js and add it to an /api folder. This is similar to the app.js file in serverful Express.js applications. The /api directory is where we'll add our Serverless Functions.

const app = require('express')()
const { v4 } = require('uuid')

app.get('/api', (req, res) => {
  const path = `/api/item/${v4()}`
  res.setHeader('Content-Type', 'text/html')
  res.setHeader('Cache-Control', 's-max-age=1, stale-while-revalidate')
  res.end(`Hello! Go to item: <a href="${path}">${path}</a>`)
})

app.get('/api/item/:slug', (req, res) => {
  const { slug } = req.params
  res.end(`Item: ${slug}`)
})

module.exports = app

Example Express.js API route api/index.js.

Notice that we added a setHeader line for our Cache-Control. This describes the lifetime of our resource, telling the CDN to serve from the cache and update in the background (at most once per second).

Let's add one rewrite to push all traffic to our index.js. Add a vercel.json at the root of your project to specify your app's behavior. Learn how to customize your Vercel projects here.

{
  "rewrites": [{ "source": "/api/(.*)", "destination": "/api" }]
}

Adding rewrites to vercel.json.

Adding a Public Directory

To serve static content, we would normally do app.use(express.static('public')) in our main file app.js. Instead, we can add a /public folder to the root.

Serving static files with Vercel allows us to do static asset hoisting and push to our Global Edge Network. We recommend using this approach instead of express.static.

Drawbacks and Edge Cases

Templating and View Engines

We might be tempted to install a view engine like Pug, EJS, or similar. Computing the same response for every request is not only expensive but highly inefficient. Remember that Serverless Functions don't keep managed state. To prevent computing on demand and ensure cached content can be used in the future, we need to set up the correct headers.

Databases

Serverless Functions will create multiple database connections as traffic increases. All connections to the database need to be consumed quickly. To ensure uptime, consider using a serverless friendly database or connection pooling.

Websockets

Serverless Functions have maximum execution limits and should respond as quickly as possible. They should not subscribe to data events. Instead, we need a client that subscribes to data events and a Serverless Function that publishes new data. Consider using a serverless friendly Realtime provider.

Errors

Express.js will swallow errors that can put the main function into an undefined state unless properly handled. They'll render their own error pages (500), which prevents Vercel from discarding the function and resetting its state.

Re-thinking our Application

Let's explore how to re-think our application to utilize serverless.

Use Multiple Serverless Functions

Vercel prevents the need for installing common dependencies you'd use with Express. For example, Vercel gives you access to the Request and Response objects from Node.js. These objects are the standard HTTP Request and Response objects from the Node.js runtime. You also have access to Node.js helpers and these are similar to the Express API.

Note: Read our Migration Guide to understand the transition to serverless.

Incrementally Migrate

It can be overwhelming migrating to a new provider, especially for new companies. We recommend adopting Vercel (and serverless) incrementally. This helps reduce risk while still pushing your product and platform forward.

Serverless Functions can be used for every part of your application, including:

Conclusion

It's possible to deploy an Express.js application as a single Serverless Function, but it comes with drawbacks and should only be used as a migration path. Instead, embrace multiple Serverless Functions as you incrementally migrate to the Vercel platform.

To deploy your Express.js project with a Vercel for Git, make sure it has been pushed to a Git repository.

Import the project into Vercel using your Git of choice:

After your project has been imported, all subsequent pushes to branches will generate Preview Deployments, and all changes made to the Production Branch (commonly "main") will result in a Production Deployment.



Written By
Written by okbelokbel
Written by leerobleerob
Last Edited on October 2nd 2020