Back to Blog
Lesson 18 of the Node.js: Build Your First Server & CLI course
Node.jsAugust 5, 20264 min read

Routing in Express: Building Scalable API Endpoints

Master Express routing to map URLs to backend logic. Learn to create GET routes, handle dynamic parameters, and organize your API for long-term scalability.

Node.jsExpressAPIroutingbackend
Dynamic 3D abstract image with geometric pattern in blue and peach tones.

Previously in this course, we covered the HTTP request-response cycle and performed the initial Express.js setup to get a server listening on a port. Now that we have a running server, we need to teach it how to handle specific requests.

Routing in Express determines how an application responds to a client request at a particular endpoint (a URI and a specific HTTP request method). If you think of your server as a switchboard, routing is the operator connecting the caller to the right department.

Defining Basic GET Routes

A route in Express is defined using methods on the app object that correspond to HTTP methods. For retrieving data, we use app.get().

The app.get() method takes two primary arguments:

  1. Path: The URL string (e.g., /users).
  2. Callback: A function executed when the request matches the path. This function receives the req (request) and res (response) objects.
JAVASCRIPT
const express = require(CE9178">'express');
const app = express();

app.get(CE9178">'/', (req, res) => {
  res.send(CE9178">'Welcome to the Task API');
});

app.get(CE9178">'/tasks', (req, res) => {
  res.json({ message: CE9178">'List of all tasks' });
});

Dynamic Route Parameters

Real-world APIs rarely serve static data. You need to fetch specific resources, such as a task with a unique ID. We handle this using route parameters, which are named URL segments used to capture values at specific positions in the URL.

You define them by prefixing the segment with a colon (:). These values are then accessible in the req.params object.

JAVASCRIPT
// Accessing a specific task by ID
app.get(CE9178">'/tasks/:taskId', (req, res) => {
  const { taskId } = req.params;
  res.json({ message: CE9178">`Fetching details for task ID: ${taskId}` });
});

If a user visits /tasks/123, req.params.taskId will equal "123".

Organizing Routes for Scalability

As your project grows, putting every route in your main index.js file will quickly become unmanageable. To keep your code clean, we use the express.Router class to create modular, mountable route handlers.

Think of the Router as a "mini-app" that handles its own set of routes.

1. Create a route file (routes/taskRoutes.js)

JAVASCRIPT
const express = require(CE9178">'express');
const router = express.Router();

router.get(CE9178">'/', (req, res) => res.send(CE9178">'Task List'));
router.get(CE9178">'/:id', (req, res) => res.send(CE9178">`Task ${req.params.id}`));

module.exports = router;

2. Mount the router in index.js

JAVASCRIPT
const taskRoutes = require(CE9178">'./routes/taskRoutes');

// All routes in taskRoutes will be prefixed with /api/tasks
app.use(CE9178">'/api/tasks', taskRoutes);

This approach allows you to group related endpoints (like users, authentication, or tasks) into separate files, making your API architecture far easier to navigate.

Hands-on Exercise

  1. Inside your project folder, create a directory named routes.
  2. Inside routes, create a file called userRoutes.js.
  3. Define a GET route at / that returns a JSON object: { users: [] }.
  4. Define a GET route at /:username that returns a message: Hello, [username]!.
  5. Import this router into your main index.js and mount it at /users.
  6. Start your server and test these endpoints using your browser or a tool like Postman.

Common Pitfalls

  • Order of Routes: Express matches routes in the order they are defined. If you define a generic route like /tasks/:id before a specific route like /tasks/active, the generic route might catch the request first. Always place more specific routes above dynamic or wildcard routes.
  • Missing next(): If your callback function doesn't send a response (e.g., res.send or res.json) and doesn't call next(), the client will hang indefinitely.
  • Hardcoding Paths: Avoid repeating base paths. Use app.use() to prefix your routes (like /api/v1) rather than typing the prefix into every single route definition.

FAQ

Q: Can I have multiple parameters in one path? A: Yes. You can define paths like /users/:userId/tasks/:taskId. Both userId and taskId will be available in req.params.

Q: What is the difference between req.params and req.query? A: req.params are for identifying a specific resource (e.g., /tasks/123). req.query is for filtering or sorting a collection (e.g., /tasks?status=completed). We will cover query parameters in a later lesson.

Recap

We've successfully moved from a single-file server to a modular, scalable architecture. You now know how to define GET routes, capture dynamic IDs using route parameters, and use express.Router to keep your project organized. Mastering this structure is essential for building a clean, professional API.

Up next: Handling HTTP Methods — we will expand our server to handle POST, PUT, and DELETE operations to make our API truly interactive.

Similar Posts