Reimplementing Express: Part 1
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.
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
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.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
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.
If you’ve used Express, you know that it provides response methods that save us the work of doing
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.
res.status() is chainable, meaning we can do
res.status(200).send('Foo!') so it’s important that
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.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!