loading...
Cover image for Writing Async/Await Middleware in Express

Writing Async/Await Middleware in Express

geoff profile image Geoff Davis Originally published at dev.to Updated on ・4 min read

Do you use Promise-based data fetching in your Express apps? Are you sick of nesting route-handling code statements inside .then() method calls? Want to learn async/await in a practical, applicable way? If your answer was "yes!" to any of those questions, read on, and I will help you move those Promises into asynchronous middleware functions.

(If you just want to see my example repository with finished code, check it out here)

The Problem

If you are anything like me, your Express router fetches data by requesting data from the respective API endpoint or database using a Promise-based approach. This is a modern approach to alleviate a problem that many developers call "callback hell".

The following snippet is what modeled after one of my current Express side projects; note the asynchronous, Promise-based structure of passing data in lieu of synchronous, function-based callbacks.

/* Example of a Promise-based data request approach */
const endpoint = 'https://jsonplaceholder.typicode.com/posts/1';
app.get('/',(req,res) => {
  PromiseBasedDataRequest(endpoint).then(data => {
    const { title, body } = data;
    req.render('post', { title, body });
  })
})

So we're using relatively flat Promises versus a pyramid of callback functions on top of callback functions, great!

But once we have our data passed through the chain of .then() calls, we now have to write a bunch of code to process the data and/or send the actual response for the router request. Some developers will handle their code this way, and if that works for you, fantastic; however, now that async/await is supported in Node, it doesn't have to be this way.

My Solution

With support for asynchronous functions (often referred to as async/await) in Node.js as of v7.6, we can now extract data directly from a resolved Promise in an async middleware function and pass that data to the final router callback in a clean and easily-readable manner.

Consider the following middleware function as an update to the previous snippet:

const endpoint = 'https://jsonplaceholder.typicode.com/posts/1';
const asyncMiddleware = async (req,res,next) => {
  const data = await PromiseBasedDataRequest(endpoint);
  req.data = data.json()
  next()
}

You may noticed we added a few things:

  1. The async keyword in front of the function declaration; this indicates to the engine that somewhere in the function body, there is a call to a Promise, and often that a Promise uses the await keyword (see #2).
  2. The await keyword in front of the PromiseBasedDataRequest call assigned to the data variable; this indicates to the engine that all other code execution should be halted until the Promise resolves. (i.e. the application "awaits" the result of the request) This also allows the PromiseValue to be assigned to a variable and used later.

After that, the data fetched from endpoint is assigned to a property on the req/request object; this allows the value to be accessed later on in the application via req.data. Finally, a call to next() sends the req and res objects to the next piece of middleware, or, when no other middleware is in place, to the final route callback.

Let's put our asyncMiddleware function in the router's chain; in Express, this happens between the route and the final callback. (you can place as many middleware functions here as you'd like, just do not forget to call next() at the end of each!)

app.get('/', asyncMiddleware, (req,res) => {
  const { title, body } = req.data;
  req.render('post', { title, body });
})

Wow! So fresh, so clean.

Now we have a very flat and readable router declaration, with the bulk of our code that would normally sit in the router callback^[1] instead situated in a middleware function, and the data being synchronously passed into the final router callback function.

If you would like to see this implementation/pattern in a "completed" project, check out the example repo I set up for this article. Follow the instructions in the README file to get started; if you find something wrong or need help, feel free to file an issue and I will be happy to review with you.

The Motivation

I was motivated to research this solution because I wanted to learn and experiment more with async/await functions and Express middleware; the best way I learn is by making test projects and having hands-on experience implementing features.

I plan to use this exact pattern of writing async middleware in a side project that utilizes the Javascript Contentful SDK and MySQL driver. The current implementation I have in this project is exactly like the first code snippet example: the respective API is called and wraps the actual res/response call in either a callback or a .then() method call. By rewriting these features into middleware functions, I aim to simplify the writing of new router paths/API endpoints and hopefully increase code reusability.

Conclusion

In closing, utilizing async/await as your Express middleware implementation helps keep your code reusable, readable, and up-to-date with current coding conventions.

I hope my little Saturday experiment helped you better understand asynchronous functions and Express middleware. Thanks for reading!

Further Reading

Notes

[1]: According to this Stack Overflow post, unresolved Promises do not pose any serious threat of a memory leak.

Discussion

pic
Editor guide
Collapse
andywer profile image
Andy Wermke

If switching frameworks in an option you might wanna check out koa v2 (koajs.com). koa v1 used generator functions, but koa v2 comes with a 100% promise-based API, so you have support for async/await route handlers and middlewares out of the box!

Express probably has the most evolved middleware ecosystem of all node server frameworks, but koa's is quite comprehensive as well :)

Collapse
josser profile image
Dmitry Chirkin

There is one thing in your code which may break: (sorry if my english is not good)

Take a look at this code:

function someethingAsync() {
  return new Promise((resolve, reject) => {
    setTimeout(() => { reject() }, 1000);
  })
}

async function test() {
  const data = await someethingAsync();
  console.log('Will you see me?')
  console.log(data);
  console.log('No')
}

test();

If you run it, you will never see console.log's
And the same is true for you middleware code
If PromiseBasedDataRequest will be rejected then you never reach next() and execution will hang.

Rule of thumb here is simple: when we trying to connect Promise-style and callback-style code and you are somewhere in callback, you should always write .catch() block.

Collapse
josheriff profile image
Jose

You don't need the "then" since the await do the job.

Also you don't need the next() at the end, if I'm not in a mistake, async express just go next when finish solving all the promises inside the function.

I'm in the mobile now but try to put an example later if you want

Collapse
geoff profile image
Geoff Davis Author

Thanks! I updated my snippets.

Regarding the next() call, I'll have to check that out.

Collapse
josheriff profile image
Jose

I mean this:

const endpoint = 'jsonplaceholder.typicode.com/posts/1';
const asyncMiddleware = async (req,res) => {
const data = await PromiseBasedDataRequest(endpoint);
req.data = await data.json() // if I remember well data.json() its a
//promise too thats why about another
//await
return req.data
}

// the workflow continues

Async await it's almost have a "syncronous" code.

Collapse
costicaaa profile image
costicaaa

Thanks for the snippet, exactly what i was looking for

Collapse
chrismarx profile image
chrismarx

I've adopted this library for automatically adding async/promise support in express routes, curious if you've looked at it and what your thoughts are -

npmjs.com/package/express-promise-...

Collapse
nax3t profile image
Ian Schoonover

Great article! Thanks :)

I noticed you have: req.render('post', { title, body });
shouldn't it be: res.render('post', { title, body }); ?

Collapse
geoff profile image
Geoff Davis Author

Good catch! I'll update my snippets

Collapse
itzhakfranco profile image
itzhakfranco

found it super helpful. Thanks

Collapse
saifalfalah profile image
Saif Al Falah

This was extremely helpful. Thanks

Collapse
nvcken profile image
Ken

Thanks your post.
As I know we need try/catch to ensure safety in async/await function so that have you know anyway to better manually put try/catch in each middeware function ?