How Does Routing Work in a Backend?

작성자

카테고리:

← 피드로
DEV Community · Tanu Priya · 2026-09-08 개발(SW)

Every time you interact with an application, there is usually a request happening somewhere in the background.

You open a product:

GET /api/products/42

Enter fullscreen mode Exit fullscreen mode

You log in:

POST /api/login

Enter fullscreen mode Exit fullscreen mode

You update your profile:

PATCH /api/profile

Enter fullscreen mode Exit fullscreen mode

You delete a post:

DELETE /api/posts/10

Enter fullscreen mode Exit fullscreen mode

But when these requests reach the backend, how does the server know which piece of code should handle which request?

That’s where backend routing comes in.

Routing is the mechanism that maps an incoming request to the code responsible for processing it.

A simple mental model is:

HTTP Request
     ↓
   Router
     ↓
Matching Route
     ↓
Middleware
     ↓
Controller
     ↓
Business Logic
     ↓
Response

Enter fullscreen mode Exit fullscreen mode

Routing sounds simple at first, but it becomes an important architectural concern as an application grows.

Let’s understand what actually happens.

1. What Is Backend Routing?

A route is essentially a rule that says:

“When a request with this HTTP method reaches this path, run this handler.”

For example:

app.get("/api/products", getProducts);

Enter fullscreen mode Exit fullscreen mode

This tells the backend:

Method: GET
Path: /api/products
Handler: getProducts

Enter fullscreen mode Exit fullscreen mode

So when the client sends:

GET /api/products

Enter fullscreen mode Exit fullscreen mode

the router finds the matching route and executes:

getProducts();

Enter fullscreen mode Exit fullscreen mode

That’s the basic idea behind routing.

But a production backend usually does much more than simply match a URL.

The request may go through authentication, authorization, validation, logging, rate limiting, controllers, services, caches, databases, and external APIs before a response is produced.

2. Why Do We Need Routing?

Imagine a backend application with hundreds of APIs.

You might have:

/api/users
/api/users/:id
/api/products
/api/products/:id
/api/orders
/api/orders/:id
/api/payments
/api/login
/api/logout
/api/notifications

Enter fullscreen mode Exit fullscreen mode

The server needs a way to distinguish between them.

For example:

GET /api/products
        ↓
Product Handler

GET /api/orders
        ↓
Order Handler

POST /api/login
        ↓
Login Handler

Enter fullscreen mode Exit fullscreen mode

Without routing, the backend would have no organized way to decide which code should process an incoming request.

Routing gives the application structure.

It creates a clear boundary between the outside world and the internal code that performs the actual work.

As the number of endpoints grows, this becomes increasingly important. A backend with 10 routes can be easy to understand even if everything is in one file. A backend with 200 or 500 routes needs much stronger organization.

3. A Route Is More Than Just a URL

A common mistake is to think:

Route = URL

Enter fullscreen mode Exit fullscreen mode

It is actually closer to:

Route = HTTP Method + Path + Handler

Enter fullscreen mode Exit fullscreen mode

For example:

app.get("/users", getUsers);
app.post("/users", createUser);

Enter fullscreen mode Exit fullscreen mode

Both routes use:

/users

Enter fullscreen mode Exit fullscreen mode

but they mean completely different things.

GET /users
    ↓
Retrieve users

POST /users
     ↓
Create a user

Enter fullscreen mode Exit fullscreen mode

The HTTP method is therefore part of the route definition.

This is why changing only the method can completely change the meaning of an endpoint.

4. Routing Starts After the Request Arrives

Suppose the browser sends:

GET /api/products/42 HTTP/1.1
Host: example.com

Enter fullscreen mode Exit fullscreen mode

The backend receives the request.

Conceptually:

Client
   ↓
HTTP Request
   ↓
Server
   ↓
Router

Enter fullscreen mode Exit fullscreen mode

The router examines information such as:

Method → GET
Path   → /api/products/42

Enter fullscreen mode Exit fullscreen mode

It then searches for a matching route.

For example:

app.get("/api/products/:id", getProduct);

Enter fullscreen mode Exit fullscreen mode

The router recognizes that:

/api/products/42

Enter fullscreen mode Exit fullscreen mode

matches:

/api/products/:id

Enter fullscreen mode Exit fullscreen mode

and passes the request to getProduct.

The router doesn’t necessarily care what happens inside getProduct. Its primary responsibility is determining where the request should go.

5. Static Routes

The simplest routes use fixed paths.

For example:

app.get("/api/products", getProducts);
app.get("/api/orders", getOrders);
app.get("/api/users", getUsers);

Enter fullscreen mode Exit fullscreen mode

These are static routes.

The path has to match the defined route.

For example:

GET /api/products

Enter fullscreen mode Exit fullscreen mode

matches:

/api/products

Enter fullscreen mode Exit fullscreen mode

but:

GET /api/product

Enter fullscreen mode Exit fullscreen mode

doesn’t.

Static routes are useful for endpoints where the resource itself doesn’t need an identifier in the path.

They are especially common for collection-level operations.

6. Dynamic Routes

What if you want to retrieve a specific product?

You could create:

/api/products/1
/api/products/2
/api/products/3
/api/products/4

Enter fullscreen mode Exit fullscreen mode

You obviously don’t want to create a separate route for every product.

Instead, you use a dynamic parameter:

app.get("/api/products/:id", getProduct);

Enter fullscreen mode Exit fullscreen mode

Now all of these can match:

/api/products/1
/api/products/42
/api/products/999

Enter fullscreen mode Exit fullscreen mode

The :id part is a route parameter.

The backend can access it:

app.get("/api/products/:id", (req, res) => {
    const id = req.params.id;

    console.log(id);
});

Enter fullscreen mode Exit fullscreen mode

For:

GET /api/products/42

Enter fullscreen mode Exit fullscreen mode

you get:

req.params.id
       ↓
      "42"

Enter fullscreen mode Exit fullscreen mode

Dynamic routes allow one route definition to handle potentially thousands or millions of resources.

7. Route Parameters Represent Resources

Dynamic routes are especially useful for REST-style APIs.

For example:

GET /users/42

Enter fullscreen mode Exit fullscreen mode

can mean:

Get user 42.

GET /users/42/orders

Enter fullscreen mode Exit fullscreen mode

can mean:

Get orders belonging to user 42.

GET /products/100/reviews

Enter fullscreen mode Exit fullscreen mode

can mean:

Get reviews for product 100.

The path can communicate relationships between resources.

A useful structure might be:

/users
/users/:id
/users/:id/orders

/products
/products/:id
/products/:id/reviews

Enter fullscreen mode Exit fullscreen mode

This makes APIs easier for developers to understand because the URL structure communicates the resource hierarchy.

8. Query Parameters Are Different

Consider:

/api/products?page=2&limit=20

Enter fullscreen mode Exit fullscreen mode

Here:

/api/products

Enter fullscreen mode Exit fullscreen mode

is the route path.

While:

?page=2&limit=20

Enter fullscreen mode Exit fullscreen mode

contains query parameters.

In Express:

app.get("/api/products", (req, res) => {
    const page = req.query.page;
    const limit = req.query.limit;
});

Enter fullscreen mode Exit fullscreen mode

So:

/api/products?page=2

Enter fullscreen mode Exit fullscreen mode

gives:

req.query.page
       ↓
      "2"

Enter fullscreen mode Exit fullscreen mode

A useful distinction is:

Path Parameter
/products/:id
        ↓
Identifies a resource

Query Parameter
/products?page=2
        ↓
Modifies, filters, sorts, or paginates the request

Enter fullscreen mode Exit fullscreen mode

For example:

/products?category=phones
/products?sort=price
/products?page=3
/products?search=keyboard

Enter fullscreen mode Exit fullscreen mode

These usually don’t represent different routes.

They are different ways of querying the same route.

9. Request Body Is Another Source of Data

For a POST request:

POST /api/users

Enter fullscreen mode Exit fullscreen mode

the client might send:

{
  "name": "Alex",
  "email": "[email protected]"
}

Enter fullscreen mode Exit fullscreen mode

This data is in the request body.

In Express:

app.post("/api/users", (req, res) => {
    const name = req.body.name;
    const email = req.body.email;
});

Enter fullscreen mode Exit fullscreen mode

Now you have three common places where request data can come from:

req.params → /users/:id

req.query  → /users?page=2

req.body   → JSON payload

Enter fullscreen mode Exit fullscreen mode

Knowing which type of data belongs where makes API design much clearer.

10. Routing and Middleware Work Together

A route usually doesn’t directly jump into business logic.

There can be middleware in between.

For example:

Request
   ↓
Logging Middleware
   ↓
Authentication Middleware
   ↓
Validation Middleware
   ↓
Router
   ↓
Controller
   ↓
Response

Enter fullscreen mode Exit fullscreen mode

Consider:

app.get(
    "/api/profile",
    authenticate,
    getProfile
);

Enter fullscreen mode Exit fullscreen mode

The request first goes through:

authenticate

Enter fullscreen mode Exit fullscreen mode

and only if that middleware allows it does the request reach:

getProfile

Enter fullscreen mode Exit fullscreen mode

This is useful because authentication doesn’t need to be manually repeated inside every handler.

Middleware creates reusable processing steps that can be shared across routes.

11. Route-Level Middleware

You can also apply middleware only to certain routes.

For example:

app.delete(
    "/api/users/:id",
    authenticate,
    requireAdmin,
    deleteUser
);

Enter fullscreen mode Exit fullscreen mode

The flow becomes:

DELETE /api/users/42
        ↓
Authentication
        ↓
Admin Check
        ↓
Delete User

Enter fullscreen mode Exit fullscreen mode

This is much cleaner than putting all those checks inside deleteUser.

The route definition itself now describes the processing pipeline.

You can almost read the route like a sentence:

Delete this user, but first authenticate the requester and verify that they are an administrator.

12. Controllers Handle the Request

As applications grow, developers usually avoid putting everything directly inside route definitions.

Instead of:

app.get("/products", async (req, res) => {
    // database query
    // business logic
    // validation
    // response
});

Enter fullscreen mode Exit fullscreen mode

you might have:

app.get("/products", getProducts);

Enter fullscreen mode Exit fullscreen mode

and:

async function getProducts(req, res) {
    // controller logic
}

Enter fullscreen mode Exit fullscreen mode

Now the architecture becomes:

Route
  ↓
Controller
  ↓
Service
  ↓
Database

Enter fullscreen mode Exit fullscreen mode

This separation becomes valuable as the codebase gets larger.

The route is responsible for mapping requests.

The controller handles the HTTP-specific part.

The service can contain business logic.

The database layer handles persistence.

Each layer has a clearer responsibility.

13. Routes Shouldn’t Usually Contain All Business Logic

Imagine this:

app.post("/orders", async (req, res) => {

    // authenticate user

    // validate product

    // check inventory

    // calculate discount

    // calculate tax

    // charge payment

    // create order

    // update inventory

    // send email

});

Enter fullscreen mode Exit fullscreen mode

It works.

But eventually this route becomes difficult to understand and test.

A better structure might be:

Route
  ↓
Controller
  ↓
Order Service
  ↓
Inventory Service
  ↓
Payment Service
  ↓
Database

Enter fullscreen mode Exit fullscreen mode

The route answers:

“Which operation should handle this request?”

The service layer answers:

“How should this operation actually work?”

This separation helps control complexity.

It also means changes to business logic don’t necessarily require changing the route structure.

14. Route Organization

A large backend might have many route files.

For example:

routes/
    users.js
    products.js
    orders.js
    payments.js
    auth.js

Enter fullscreen mode Exit fullscreen mode

Then the main application can combine them:

app.use("/api/users", userRoutes);
app.use("/api/products", productRoutes);
app.use("/api/orders", orderRoutes);

Enter fullscreen mode Exit fullscreen mode

Inside productRoutes:

router.get("/", getProducts);
router.get("/:id", getProduct);
router.post("/", createProduct);
router.patch("/:id", updateProduct);
router.delete("/:id", deleteProduct);

Enter fullscreen mode Exit fullscreen mode

This produces:

/api/products
/api/products/:id

Enter fullscreen mode Exit fullscreen mode

while keeping product-related routing in one place.

This kind of organization becomes especially useful when multiple developers are working on the same backend.

15. Route Prefixes Reduce Duplication

Instead of writing:

router.get("/api/products", ...);
router.get("/api/products/:id", ...);
router.post("/api/products", ...);

Enter fullscreen mode Exit fullscreen mode

you can mount the router:

app.use("/api/products", productRoutes);

Enter fullscreen mode Exit fullscreen mode

Then inside:

router.get("/", getProducts);
router.get("/:id", getProduct);
router.post("/", createProduct);

Enter fullscreen mode Exit fullscreen mode

The backend combines them:

/api/products + /
        ↓
/api/products

/api/products + /:id
        ↓
/api/products/:id

Enter fullscreen mode Exit fullscreen mode

This makes route organization much cleaner.

It also gives each resource its own boundary.

16. Route Matching Order Can Matter

Suppose you have:

router.get("/:id", getProduct);
router.get("/featured", getFeaturedProducts);

Enter fullscreen mode Exit fullscreen mode

Depending on the framework and routing rules, a request like:

/featured

Enter fullscreen mode Exit fullscreen mode

could potentially match the dynamic route first.

A safer ordering is often:

router.get("/featured", getFeaturedProducts);
router.get("/:id", getProduct);

Enter fullscreen mode Exit fullscreen mode

The broader lesson is:

Routing rules are evaluated according to the framework’s matching behavior, so route specificity and ordering can matter.

This becomes particularly important when you have dynamic parameters mixed with special static paths.

17. What Happens If No Route Matches?

Suppose the client requests:

GET /api/does-not-exist

Enter fullscreen mode Exit fullscreen mode

and no route matches.

The backend should return an appropriate response, commonly:

404 Not Found

Enter fullscreen mode Exit fullscreen mode

You might have a fallback handler:

app.use((req, res) => {
    res.status(404).json({
        error: "Route not found"
    });
});

Enter fullscreen mode Exit fullscreen mode

Conceptually:

Request
   ↓
Router
   ↓
Any matching route?
   |
   ├── Yes → Handler
   |
   └── No → 404

Enter fullscreen mode Exit fullscreen mode

This makes it clear to the client that the requested endpoint doesn’t exist.

18. Error Handling Happens After Routing Too

A route can match correctly and still fail.

For example:

GET /api/products/42
        ↓
Route matches
        ↓
Database query
        ↓
Database fails
        ↓
Error Handler
        ↓
500 Response

Enter fullscreen mode Exit fullscreen mode

In Express, applications often have centralized error-handling middleware.

The idea is:

Route
  ↓
Controller
  ↓
Service
  ↓
Error
  ↓
Central Error Handler
  ↓
HTTP Response

Enter fullscreen mode Exit fullscreen mode

This prevents every route from having to implement completely different error formatting.

It also gives the API a consistent error structure.

For example:

{
  "error": "Something went wrong"
}

Enter fullscreen mode Exit fullscreen mode

A consistent API is easier for frontend and mobile developers to consume.

19. Routing Is Not the Same as Business Logic

This distinction is worth remembering.

Routing answers:

Where should this request go?

Business logic answers:

What should happen after it gets there?

For example:

POST /api/orders
      ↓
Routing
      ↓
Order Controller
      ↓
Order Service
      ↓
Check inventory
      ↓
Calculate price
      ↓
Create order

Enter fullscreen mode Exit fullscreen mode

The route doesn’t need to know every detail about creating an order.

It only needs to send the request to the correct part of the application.

This separation keeps the routing layer relatively simple even when the underlying business operation is complicated.

20. Authentication Often Starts Around the Route

Consider:

GET /api/profile

Enter fullscreen mode Exit fullscreen mode

A backend might process it as:

Request
   ↓
Route
   ↓
Authentication
   ↓
Controller
   ↓
User Service
   ↓
Database
   ↓
Response

Enter fullscreen mode Exit fullscreen mode

The route identifies the operation.

Authentication identifies the user.

The service retrieves the relevant information.

The controller turns the result into an HTTP response.

This separation is one of the reasons layered backend architectures are easier to reason about.

21. Routing and REST API Design

Good routing also makes APIs easier to understand.

Instead of creating routes like:

/getAllProducts
/getProductById
/createNewProduct
/deleteProduct

Enter fullscreen mode Exit fullscreen mode

a REST-style API might use:

GET    /products
GET    /products/:id
POST   /products
PATCH  /products/:id
DELETE /products/:id

Enter fullscreen mode Exit fullscreen mode

The HTTP method communicates the operation.

The path represents the resource.

So:

GET /products/42

Enter fullscreen mode Exit fullscreen mode

means:

Retrieve product 42.

while:

DELETE /products/42

Enter fullscreen mode Exit fullscreen mode

means:

Delete product 42.

The same resource can therefore have multiple operations without creating completely different naming conventions.

22. Nested Routes Represent Relationships

Sometimes resources are related.

For example:

GET /users/42/orders

Enter fullscreen mode Exit fullscreen mode

can represent the orders belonging to user 42.

Similarly:

GET /products/10/reviews

Enter fullscreen mode Exit fullscreen mode

can represent reviews belonging to product 10.

The structure communicates the relationship:

User
  ↓
Orders

Product
  ↓
Reviews

Enter fullscreen mode Exit fullscreen mode

However, deeply nested routes can become difficult to work with.

For example:

/users/42/orders/10/items/5/reviews

Enter fullscreen mode Exit fullscreen mode

may technically work, but it can become unnecessarily complicated.

Good API design usually aims for routes that are clear without making the URL hierarchy excessively deep.

23. Versioning Routes

APIs sometimes need to evolve without immediately breaking existing clients.

You might see:

/api/v1/users
/api/v2/users

Enter fullscreen mode Exit fullscreen mode

For example:

app.use("/api/v1/users", userRoutesV1);
app.use("/api/v2/users", userRoutesV2);

Enter fullscreen mode Exit fullscreen mode

This allows different clients to use different API versions while the backend evolves.

API versioning is only one strategy, but routing provides a natural place to express these boundaries.

This becomes particularly useful when an API has mobile clients that cannot all be updated at the same time.

24. Routing at Scale

A small application might have:

Client
  ↓
One Backend
  ↓
Database

Enter fullscreen mode Exit fullscreen mode

A larger system might have:

                    Load Balancer
                         ↓
              ┌──────────┼──────────┐
              ↓          ↓          ↓
           API 1      API 2      API 3
              ↓          ↓          ↓
           Services   Services   Services
              ↓          ↓          ↓
          Databases / Caches / Queues

Enter fullscreen mode Exit fullscreen mode

At this point, routing can happen at multiple levels.

For example, an API gateway might route:

/api/users
      ↓
User Service

/api/orders
      ↓
Order Service

/api/payments
      ↓
Payment Service

Enter fullscreen mode Exit fullscreen mode

So routing isn’t limited to a single Express router.

The same fundamental idea appears throughout distributed systems:

Look at the request and determine where it should go.

25. Routing Inside a Microservices Architecture

In a monolithic backend, routing might look like:

Client
   ↓
Backend Application
   ↓
Router
   ↓
User / Order / Product Code

Enter fullscreen mode Exit fullscreen mode

With microservices, the architecture can look different:

Client
   ↓
API Gateway
   ↓
 ┌───────────────┐
 ↓       ↓       ↓
User   Order   Payment
Service Service Service

Enter fullscreen mode Exit fullscreen mode

Now the first routing decision might happen at the API gateway.

The gateway sees:

/api/users

Enter fullscreen mode Exit fullscreen mode

and sends it to the User Service.

For:

/api/orders

Enter fullscreen mode Exit fullscreen mode

it sends the request to the Order Service.

The Order Service might then perform additional internal routing or service-to-service communication.

The core idea hasn’t changed.

The system still needs to answer:

Where should this request go?

Only the scale and number of routing layers have changed.

26. Routing Can Also Help With Traffic Control

Routing isn’t always just about finding a piece of code.

At infrastructure level, traffic can be routed based on different rules.

For example:

Incoming Traffic
       ↓
Load Balancer
       ↓
 ┌─────┼─────┐
 ↓     ↓     ↓
Server Server Server

Enter fullscreen mode Exit fullscreen mode

Traffic might be distributed across multiple backend instances.

At a larger level, requests might be routed based on:

  • hostname
  • URL path
  • service
  • region
  • deployment version
  • availability

This is where routing starts becoming a system-design concept rather than just a framework feature.

27. Routing and Security

Routing also creates important security boundaries.

For example:

/api/public/*

Enter fullscreen mode Exit fullscreen mode

might be publicly accessible.

While:

/api/admin/*

Enter fullscreen mode Exit fullscreen mode

might require authentication and administrator permissions.

You can structure middleware accordingly:

/api/public
      ↓
Public Routes

/api/users
      ↓
Authentication
      ↓
User Routes

/api/admin
      ↓
Authentication
      ↓
Authorization
      ↓
Admin Routes

Enter fullscreen mode Exit fullscreen mode

The route structure can therefore make security requirements easier to understand.

However, simply hiding or naming a route as /admin does not provide security by itself.

The backend still needs to actually enforce authentication and authorization.

28. One Request, End to End

Let’s put everything together.

A user clicks:

“View Product”

The browser sends:

GET /api/products/42

Enter fullscreen mode Exit fullscreen mode

The backend might process it like this:

                    HTTP Request
                         |
                         ↓
                    Load Balancer
                         |
                         ↓
                     Backend
                         |
                         ↓
                    Middleware
                         |
                         ↓
                      Router
                         |
                GET /products/:id
                         |
                         ↓
                    Controller
                         |
                         ↓
                      Service
                         |
                 ┌───────┴───────┐
                 ↓               ↓
               Redis          Database
                 |               |
                 └───────┬───────┘
                         ↓
                      Response
                         |
                         ↓
                       Client

Enter fullscreen mode Exit fullscreen mode

What looked like:

GET /api/products/42

Enter fullscreen mode Exit fullscreen mode

was actually the entry point into an entire processing pipeline.

The router was responsible for finding the correct path through that system.

29. What Happens When You Type a URL?

Suppose you visit:

https://example.com/api/products/42

Enter fullscreen mode Exit fullscreen mode

A simplified backend journey is:

Browser
   ↓
HTTP Request
   ↓
Load Balancer
   ↓
Backend
   ↓
Router
   ↓
GET /api/products/:id
   ↓
Middleware
   ↓
Controller
   ↓
Service
   ↓
Database
   ↓
Response
   ↓
Browser

Enter fullscreen mode Exit fullscreen mode

The router is the component that recognizes:

GET /api/products/42

Enter fullscreen mode Exit fullscreen mode

as belonging to:

GET /api/products/:id

Enter fullscreen mode Exit fullscreen mode

and sends it to the correct handler.

That’s the core of backend routing.

30. The Bigger Picture

When you first learn Express or another backend framework, routing can look like a few simple lines:

app.get("/users", getUsers);
app.post("/users", createUser);
app.delete("/users/:id", deleteUser);

Enter fullscreen mode Exit fullscreen mode

But those lines are actually defining the public interface of your application.

They tell clients:

Which resources exist?
Which operations are supported?
Which URLs represent those resources?
Which HTTP methods should be used?

Enter fullscreen mode Exit fullscreen mode

As the application grows, these decisions become part of API design.

Poorly designed routes can make an API confusing.

Well-designed routes make it easier for developers to understand how the system works without reading the backend implementation.

A Simple Mental Model

Whenever you see:

GET /api/products/42

Enter fullscreen mode Exit fullscreen mode

think:

HTTP Request
     ↓
Method + Path
     ↓
Router
     ↓
Find Matching Route
     ↓
Middleware
     ↓
Controller
     ↓
Business Logic
     ↓
Database / Cache / Services
     ↓
HTTP Response

Enter fullscreen mode Exit fullscreen mode

Routing is essentially the traffic director of your backend.

It doesn’t necessarily perform the actual business operation.

Instead, it determines where the request needs to go.

That sounds simple, but as an application grows, good routing becomes increasingly important.

Clear routes make APIs easier to understand.

Well-organized route modules make codebases easier to maintain.

Middleware keeps cross-cutting concerns separate.

Controllers and services prevent route handlers from becoming massive.

And at the system-design level, routing allows traffic to be directed between different services and infrastructure components.

The next time you write:

app.get("/api/users/:id", getUser);

Enter fullscreen mode Exit fullscreen mode

don’t think of it as just one line of framework syntax.

You’re defining a rule:

When this kind of request arrives, this is where the application should send it.

That’s what backend routing really does.

It is the bridge between an external HTTP request and the internal logic of your application.

And once you understand that bridge, designing APIs and understanding backend architecture becomes much easier.

원문에서 계속 ↗