Phase 3: Backend & APIs

Express routing, middleware & error handling

Intermediate ~3 min read
Think of it this way A friendly analogy. Read this if the technical version feels dense. Show Hide

Imagine you're building a super cool restaurant that takes orders from all over the internet. You need a way to know what people want to eat and how to make it happen!

First, think of your restaurant's menu. When someone asks for "tacos" or "pizza," your menu tells the kitchen exactly which recipe to use. This is like Express routing. When your website gets a request, say for /users (which means "show me the list of users") or /games/new ("create a new game"), routing is like your menu that directs that request to the specific "recipe" or set of instructions for handling it. You can have a recipe for showing users and a completely different recipe for creating a new user, even if they both involve "users." It's how your website knows what to do for each specific thing someone asks for, like getting food or saving a high score.

Now, before the main chef starts cooking your perfect dish, there are often lots of important steps that need to happen. Maybe an order checker makes sure the request is readable, or a prep chef chops all the vegetables. These are like middleware. They are little helper chefs, or steps, that your order (request) passes through before it reaches the main chef (the final recipe instructions). Each helper chef does a specific job, like checking if the customer is old enough to order a certain drink, or making sure the ingredients listed on the order slip are actually in stock. Once they're done, they politely hand the order slip to the next helper chef, or even the main chef, to continue the process. This helps keep the kitchen organized and makes sure everything is ready for the main cooking.

But what if something goes wrong? What if the customer tries to order a "flying spaghetti monster," which isn't on your menu? Or what if the prep chef runs out of tomatoes for the salad? You don't want the kitchen to just explode or for the customer to get a blank plate! This is where error handling comes in. It's like having a plan for when things don't go perfectly. If the order checker sees a strange order, they send it back to the waiter to clarify. If the prep chef finds missing ingredients, they quickly tell the main chef so a different plan can be made, or the customer can be informed politely.

So, when you're building your own websites and online tools, you'll use routing to create your menu of possibilities, middleware to organize all the behind-the-scenes steps that prepare and check things, and error handling to make sure your users always get a clear and helpful response, even if something unexpected happens.

Express routing is the backbone of defining how your application responds to client requests at specific endpoints. It allows you to map HTTP methods (like GET, POST, PUT, DELETE) to specific URL paths, each handled by a dedicated function. For instance, app.get('/users', ...) would handle fetching user data, while app.post('/users', ...) would handle creating a new user. Each route handler receives req (request) and res (response) objects, enabling you to access incoming data and send back appropriate responses, forming the core interaction of your API.

Middleware functions are the power tools of Express. They are functions that execute in sequence before your final route handler. Each middleware has access to the req and res objects, and a next() function. This next() call is vital; it passes control to the next middleware in the stack or the final route handler. Middleware can perform various tasks: logging requests, parsing JSON bodies (express.json()), authenticating users, validating input, or adding properties to the req object for subsequent handlers. They provide a clean, modular way to process requests step-by-step.

Effective error handling is critical for any robust API. In Express, this is achieved using special middleware functions that take four arguments: (err, req, res, next). When an error occurs within a route handler or another middleware (e.g., by calling next(error)), Express skips subsequent regular middleware and routes, immediately invoking the error-handling middleware. It's crucial to place these error handlers after all your regular routes and middleware. This centralized approach allows you to catch errors, log them, and send consistent, user-friendly error responses (e.g., a JSON object with a status code and message) instead of exposing sensitive internal server details.

Key Takeaways

  • Routing defines how your API responds to different HTTP methods and paths.
  • Middleware functions execute sequentially, performing tasks like logging, authentication, or data parsing using req, res, and next().
  • The next() function is essential for passing control through the middleware chain.
  • Error handling middleware (err, req, res, next) catches errors and provides consistent responses.
  • The order of middleware and routes matters significantly for correct execution and error handling.

Code Example

javascript
Preview

How this code works

This Express.js application sets up a basic web server to demonstrate how requests flow through middleware, are handled by routes, and how errors are managed. It begins by initializing express and creating an app instance. The first app.use defines a Request Logger middleware. This function runs for every incoming request, logs its method, URL, and timestamp to the console, and then crucially calls next() to pass control to the next part of the request processing pipeline. This global middleware ensures all requests are logged before any specific routing logic is applied.

Next, the app.get('/users/:id', ...) defines a specific route handler for GET requests to /users/ followed by an ID. It extracts the userId using req.params.id. If the ID is '123', it responds with user data using res.json(). Otherwise, it explicitly signals an error by calling next(new Error('User not found')). This directs control to the special Error handling middleware, which is also an app.use but with four arguments ((err, req, res, next)). This middleware must be placed last in the app.use chain; it catches any errors passed with next(err), logs the error stack with console.error, and sends a 500 status response with an error message using res.status().json(). Finally, app.listen starts the server.