Initially, the concept of routing was easy for me to grasp:
- A user clicks on a DOM element
- That element triggers a request of some sort
- The server responds to the request appropriately
In a sentence: A click routes you to anywhere you want to go.
Simple.
I think we can all agree that that's not too difficult of an idea to grasp, however when it came to the implementation of these routes, I was completely lost.
The software development program I've been in the past few months initially taught us to write routes using raw node, (senior developers: let that sink in) and because I was completely immersed in code at that time, it was just another sprint in which I grasping at straws to understand and complete in an allotted time. Through my anxiety, exhaustion and being so wrapped up in the details, I couldn't tell which "route" to follow.
My senior partner already had this concept down to a science, but in time, with more practice (and less studying!) I've finally come to understand this important technique, and also why we initially wrote these request routes without the help of Express.
I can still feel empathy for my former less-enlightened self and can understand even now how the concept of middleware, routing, and endpoints can be tricky AF to grasp, especially when I had only just recently learned what API's are, but hopefully this post will provide insight and be a point of reference for you in the future.
Routing in Node
Express is a lightweight framework for Node that abstracts away a lot of code that we would initially have to write to respond to requests from the client. Take a look at this little snippet of a GET request written in raw Node:
const defaultHeaders = {/*...*/};
const requestHandler = (req, res) => {
let statusCode;
if (req.method === 'GET') {
if (req.url === '/classes/messages') {
res.writeHead(200, defaultHeaders)
res.end(JSON.stringify(data))
}
}
}
While we can very easily see exactly what happens on each GET request to the 'classes/messages' endpoint, we can also probably see how easy it could be for this method to become exponentially extensive once other request types are accounted for.
This method didn't feel helpful or important early on, but it is! It's the only way we can really appreciate all the work Express does for us under the hood.
Our sprint that required using this antiquated method mercifully only needed responses for a few routes, but again, what if we had to deal with route requests on a much larger scale? Check out this graphic that displays the structure of Facebook's routes:
Having to route with Node in such a massive code base would be...punishment.
Thankfully, Express steps in to abstract away much of the code that is needed to respond to client requests, which in turn provides the ability for us to keep our code modular and DRY.
Routers / Middleware
Express allows for additional abstractions via its use of application-level middleware. The standout feature here is a Router (service), a function that has access to the Request and Response object, and routes the requests from the client to the appropriate handler. We can clean up our code by using routers, and make logic easy to understand for future contributors who may want to iterate on our legacy projects.
Express handler functions are written in correspondence to the HTTP object, so the methods should appear familiar and be pretty intuitive as to what they do.
Each handler takes a path
and a callback
as arguments, and must be written in sequential order due to Express' chain-like scope.
//index.js
const express = require('express');
const app = express();
const port = 3000;
//Keep routes in a separate file for maximum modularity
const router = require('./routes');
//register the middleware [file] in the app
app.use('/', router);
app.listen(port, () => console.log(`Express app is listening on port ${port}!🛸`))
Now, instead of having to jam all of our routes in the index.js
file, we can simplify and separate out the logic to store all of our handling functions in a routes.js
file.
//routes.js
var express = require('express')
var router = express.Router()
/*Not necessary but a great way to keep track of what's happening on each request*/
router.use(function timeLog (req, res, next) {
console.log(`Starting a new route to ${req.method} resources: ${req.url}`, Date.now())
next()
});
router.get('/', function (req, res) {
res.send('Hello World!')
})
router.post('/', function (req, res) {
res.send('You're logged in!')
})
module.exports = router;
When we run the server using node index.js
and navigate to localhost:3000
in the browser, we see the message 'Hello World!' appearing on the DOM, even though the function that handles the request to GET
the home page is in a different file.
Not only has the request
object responded to our client's request as we expected, but by using the timeLog
function, we also see helpful information in the console for each request.
Starting a new route to GET resources: / 1579450161070
Express helps us ensure that our clients get the information they're asking for with the expected response as well and not just :
The Router in routes.js
responds to each request and calls the next()
function in the chain that needs to respond to this request.
Endpoints
When constructing routes, remember that the standard naming convention is that endpoints should follow the rules of REST and be named relative to whatever the resource at that endpoint returns.
For example, if you're working with users, the endpoint would be /users
, for a specific user, the endpoint would be /users:userId
with the colon denoting a variable.
The common construct for denoting the home or index page is simply by using /
.
Endpoints ensure that our users wind up in places we've actually marked in our path for them, and that they also receive the information they expect, otherwise, they can run into 400 and 500 as a result.
In Conclusion
Save yourself some keystrokes and route using Express. Abstract away additional logic and separate your concerns by using the Router middleware service, and keep your naming conventions relative when it comes to creating endpoints.
If you make things even more productive, do yourself a favor and install the express-visualizer
tool. Via this helpful, interactive graphic, we can see exactly how our routes break down, the requests they respond to, and what each of them may need in addition to what they already have.
I hope this post is helpful and helps you get on the right path when it comes to routing:)
Thanks for reading!
Top comments (0)