Express Node.js

5 best practices for building a modern API with Express

4 min read

Last Updated: April 6, 2021

Express is relatively simple to get up and running. It also provides plenty of flexibility, making it a popular choice when choosing a framework to build an API with. Because of this, there are many tutorials and courses available which will teach you how to build an API with Express, however they can leave you uncertain about what best practices you should be considering for things like validation and error responses.

The following best practices will help you when designing a new API with Express, as well as with improving an existing one. Even better, these best practices will set you up with some features that other newer frameworks provide, but which aren’t included out-of-the-box with Express. Let’s get stuck in!

Jump links

Enable the full use of promises and async / await

Express technically works fine if you use promises and async / await in your middleware or route handlers. However, you must write extra code to catch the error that gets thrown when an awaited promise rejects, and then you must call the next() function with that error. If you don’t, the request will likely hang and no response will be sent to the client. This can can get quite messy and is easy to forget about.

There are two different packages which you can use to add support for async code to your Express application:

  • express-async-errors – This package monkey patches the internals of Express to add support for promises. It doesn’t need any configuration: just require it after express and you’re good to go.
  • express-async-handler – If you don’t like the idea of monkey patching the internals of Express, this package takes a different approach approach: it provides an asyncHandler() function which you can use to wrap all middleware and route handler functions.

Both of these packages will allow you to write code in your Express applications which uses promises and async / await, with much less risk of uncaught promise rejections occurring.

You can learn about what’s needed to be able to write async code safely in my article Are you using promises and async / await safely in Node.js?.

2. Validate request data with JSON Schema

You should never just trust data which is is sent in a request to your API as it could easily contain mistakes, or worse it might contain malicious data which has been crafted by an attacker in an attempt to crash your application or steal data. This means that you should always validate any data which is sent to your API before you do anything else with it e.g. store it in a database.

JSON Schema is an established standard which you can use to describe the format that you expect data to be in – a "schema". If data fails validation against a schema you will be provided with detailed error messages which you can then pass on to the client in your API response. JSON Schema is very powerful, allowing you to create schemas which validate complex data structures, however a schema could be as simple as checking that a piece of data is a string, with a schema like this:

{ "type": "string" }

The express-json-validator-middleware package brings support for JSON Schema into your application. It enables you to validate requests to your API against any schemas that you define and configure it to use.

If you want to learn how you can use JSON Schema to validate requests in your Express API, I’ve written an article which shows you how.

3. Use an existing format for error responses

It’s very tempting when building an API to invent your own format for error responses, but HTTP response status codes are a great starting point as they can communicate a specific error state. If you need to provide additional context beyond this about why the error occurred – and perhaps what can be done to resolve the issue, in the case of a client error – it’s worth considering applying the Problem Details specification. It’s a specification for an error response format from HTTP APIs, meaning that you don’t need to come up with your own. Here’s an example response using this format:

HTTP/1.1 400 Bad Request
Content-Type: application/problem+json
Content-Language: en

    "type": "",
    "title": "Your request parameters didn't validate.",
    "invalid-params": [
            "name": "age",
            "reason": "must be a positive integer"
            "name": "color",
            "reason": "must be 'green', 'red' or 'blue'"

The Problem Details specification is covered in RFC7807 – Problem Details for HTTP APIs.

You can learn how to create and send Problem Details error responses in my blog post Send awesome structured error responses with Express.

4. Send CORS response headers so web pages can call your API

If you want front end JavaScript on a web page to be able to make requests to your API, you’ll typically need your API to send CORS (Cross-Origin Resource Sharing) headers in the response. These headers tell web browsers whether it is ok or not for the web page making the request to access the contents of the API response.

You can add the cors middleware package to your application to help you send the correct CORS response headers from your API endpoints. By default, the headers it sends will allow any web page to make requests to your API, so make sure you check out the configuration options, and at the very least set the origin option so that you are restricting which web pages are able to call your API (unless you’re running an API for public use, in which case this won’t be an issue).

I’ve written a handy guide to what CORS is, how it works and how to integrate the cors middleware into your application: How to fix those confusing CORS errors when calling your Express API.

5. Separate your concerns

This is an important design principle to apply when building any type of software: split up your code into distinct modules, with single purposes and well defined interfaces. When building an API with Express it’s easy to make the mistake of mixing multiple concerns in a single module e.g. Express app configuration, route definitions, route handler middleware, database calls. Just because you can do this definitely doesn’t mean that you should! Building an application in this way will make your code much harder to test, debug, maintain and extend in the future.

If you want to see what a clear separation of concerns can look like for an Express based API, sign up to my mailing list and I’ll send you a link to the code for an example application which demonstrates this.

It is certainly possible to refactor your application later on and separate the concerns, but if you can consider how you want to do this early on, while planning and designing your API, it will result in a much more stable foundation for future development.

10 replies on “5 best practices for building a modern API with Express”

I’ve honestly never validated request data with JSON schema but it sounds like an important thing to do and it looks like the express middleware makes it quite easy to do. ALSO I’m liking the sound of the error response specifications. Great blog post!

Thanks, Johan.

I’ve not worked with OpenAPI specifications before, but I did see that GitHub released one for their REST API recently and I really like the approach. That middleware looks like a really good way of validating requests _and_ responses, which is something that the newer Fastify framework offers. Thanks for sharing this!

Great article, I was wondering how to validate input form before sending it to mongo without using mongoose and the JSON schema can do the job perfectly, thanks! I’m not sure if this is the place to go for advice, but it looks like you have some experience with express and I have a recurring error in my express app and no one on stackoverflow seems to be able to answer it. I am sharing the link in case you know the solution to this problem, and apologize in advance if this is not the appropriate place :

Thanks, David, I’m glad you found this article useful. I’ve taken a quick look at your post, but I can’t immediately see what’s causing the issue – I’ll take a more detailed look over the weekend and let you know if I spot anything then.

In the meantime, if anyone else is able to help David out, please take a look at the link he’s posted above.

Comments are closed.