Reimplementing Express: Part 1

A magical reimplementation of the popular NodeJS Framework

A couple of weeks ago I decided to begin reimplementing Express as a side project. This is something the teaching team and I had experimented briefly with before at Northcoders and I had it pegged as a fun, if challenging, little project for when I had some time to spare.

So what is Express? It’s a lightweight framework for NodeJS serverside application development. I use it regularly for building APIs and serving applications. It’s straightforward to use and is built on a model of middlewares — functions through which the request and response objects are passed through in order (array, anyone?) when your server receives a request, until you decide to respond.

If you’re confident with the JavaScript language, with how HTTP works, and with how Express itself works, I think it’s the perfect project. It has lots of small, easily identifiable features that build on one another really nicely, and gives you plenty of opportunities to solve small but tricky problems and use a range of JS language features. I’ve got about 2/3 of the way through the features I wanted to implement and I’ve used recursion, constructor functions, complex regular expressions, and got to grips with the Node HTTP module. So if you think it sounds fun then I’d encourage you to go off and try it — but if you don’t feel like it but want to know a bit more about Express and how it works internally… read on!

First off, here are the features I wanted to implement at the start:

  • Can get a new Hexpress app
  • Custom response methods
  • Default 404 error handling
  • Basic app.get() routing
  • app.put(), app.post(), app.delete(), app.all() routing
  • Support custom middleware
  • Support custom error middleware
  • Support parameterised routes
  • Support queries
  • Support serving static files
  • Support using a template engine
  • Hexpress.Router

Yes, I decided to call my clone ‘Hexpress’ mostly so that I could scatter spooky words and Halloweeny puns throughout the code. 🎃

In this blog series I’ll talk through the features I implement and share some annotated code.

So, where to start?

const app = express();

This is the first line you need to begin with Express. It creates a new Express app, which has a number of methods you might be familiar with — app.listen(), app.get() and app.use() for example. app.listen is the function you call with a port and an optional callback:

As it turns out, Express’s app.listen is identical to Node’s Http.Server.listen, and app.listen actually returns an instance of Node’s HTTP Server, so the Node HTTP module makes this part unexpectedly straightforward:

When we call hexpress() we get back an object with a listen method. The listen method just passes on any arguments you give it to Node.Server’s listen method. And then it returns the Node server itself, just in case you want it. It could be handy — it has methods such as close and getConnections .

As an improvement, we could maybe not create a new listen method every time an Express app is created. I mean… I don’t think it’s a big deal because how many Express apps does one realistically create in a single project? But let’s refactor this anyway to share methods on the prototype…

And you can see here that whenever the Node Server receives an HTTP request, it now responds too. It just echoes back the method and the path for now.

res.status(200).send(‘All good!’);

If you’ve used Express, you know that it provides response methods that save us the work of doing res.write and res.end and all of that fiddly stuff. We should add those handy methods onto the response object. At the same time, we could add any other interesting response methods, too.

res is just an object, so we can just pass the reference to it around, add some methods, to it, and then return it for future use:

So what does addResMethods do? Well, just adds those handy methods to the response object. There are lots of methods you could add, but for starters I liked the look of res.sendFile which, yes, sends a file, and sendStatus, which sets the status code and also sends back an appropriate text response, res.set which sets the response headers for you, as well as good old res.send which can take a Buffer, string, object or array and send them back to the user. All these methods do are provide abstractions over the methods that come on the Node Response Object, nothing fancy at all.

Notice that res.status() is chainable, meaning we can do res.status(200).send('Foo!') so it’s important that res.status returns this which is the request object, so that we can chain on further methods.

You’ll also see a couple of helper functions in there — divineContentType which takes a file path such as ‘public/foo/index.html’ and returns the correct content type for the header such as “text/html”, and getAppropriateTextResponse which takes a status code and returns a simple but appropriate text string to accompany it, if you just can’t be bothered to set it yourself. E.g. status 201 gives the text “Created”, and 418 gives “I’m a teapot”. These helper functions are just very boring switch statements.

Now we’re getting towards something that looks a bit like Express, but we’re still missing those critical app.get , app.put , app.post methods that allow us to set up the functions to handle requests on specific routes.

That’s what I’ll talk about in my next blog post in this series!

Thanks for reading and I’d love to know your thoughts, especially if you’ve tried this project out for yourself! What would you do differently? What have I missed about how Express works? 🦄

You can see the full source code that I’ve completed so far here.

In Part 2 I talk about how to implement the app.get() method!