Lesson 8 of 5024 min read

NestJS Controllers Tutorial – Handling Routes and Requests

Learn how NestJS controllers handle HTTP requests, define routes with decorators, and extract data from params, query strings, and request bodies.

Author: CodersNexus

NestJS Controllers Tutorial – Handling Routes and Requests

Controllers are the front door of every NestJS application. They are responsible for receiving incoming HTTP requests, understanding what the client is asking for, and returning an appropriate response, typically after delegating the actual work to a service.

This lesson covers how to build controllers that handle full CRUD (Create, Read, Update, Delete) operations, how to extract data from three different parts of a request, route parameters, query strings, and the request body, and how these pieces come together to form a realistic REST API resource.

NestJS Controllers: Learning Objectives

  • Create a NestJS controller and define its base route path.
  • Implement GET, POST, PUT, and DELETE routes for a resource.
  • Extract dynamic route parameters using the @Param() decorator.
  • Extract query string values using the @Query() decorator.
  • Extract request body data using the @Body() decorator.

NestJS Controllers: Key Terms and Definitions

  • Controller: A class decorated with @Controller() responsible for handling incoming HTTP requests and returning responses for a specific resource or route group.
  • Route parameter: A dynamic segment of a URL path, such as :id in /users/:id, extracted using @Param().
  • Query string: The part of a URL after a question mark containing key-value pairs, such as ?page=2, extracted using @Query().
  • Request body: Data sent by the client in the body of a POST, PUT, or PATCH request, typically as JSON, extracted using @Body().
  • CRUD: An acronym for Create, Read, Update, Delete, the four basic operations most REST APIs implement for a resource.
  • HTTP method decorator: Decorators like @Get(), @Post(), @Put(), @Delete(), and @Patch() that map a controller method to a specific HTTP verb.

How NestJS Controllers Works: Detailed Explanation

A controller in NestJS is declared using the @Controller() decorator, optionally passed a base path string. For example, @Controller('users') means every route defined inside this controller class is automatically prefixed with /users. Inside the controller, individual methods are decorated with HTTP method decorators like @Get(), @Post(), @Put(), and @Delete(), each optionally accepting an additional path segment appended to the controller's base path.

A typical REST resource implements four or five standard operations. GET /users returns a list of all users. GET /users/:id returns a single user by their ID, using a dynamic route parameter. POST /users creates a new user, reading data from the request body. PUT /users/:id updates an existing user, combining both a route parameter and a request body. DELETE /users/:id removes a user by ID.

To access these different pieces of incoming request data, NestJS provides dedicated parameter decorators used directly inside a route handler's method signature. @Param('id') extracts the value of the :id segment from the URL path. @Query('page') extracts a query string value like ?page=2. @Body() extracts the entire parsed JSON request body, or @Body('email') extracts just a specific field from it.

These decorators are what make NestJS controllers so readable compared to manually parsing req.params, req.query, and req.body as you would in plain Express. Instead of digging into a generic request object, you declare exactly which piece of data your method needs directly in its signature, and NestJS handles the extraction automatically, with full TypeScript type safety on top.

Interview-Friendly Explanation

A strong interview or viva answer for nestjs controllers must go beyond a one-line definition. Interviewers evaluating BCA, MCA, B.Tech, and self-taught backend developers reward answers that combine a precise definition, a clear working explanation, a real-world example, and a conclusion that highlights why it matters in production Node.js applications. Use the four-point framework below when answering under time pressure.

  • Definition point: Controller: A class decorated with @Controller() responsible for handling incoming HTTP requests and returning responses for a specific resource or route group.
  • Working point: Implement GET, POST, PUT, and DELETE routes for a resource.
  • Example point: E-commerce platforms implement nearly identical controller patterns for resources like products, categories, and orders, following the same GET/POST/PUT/DELETE structure shown here.
  • Conclusion point: Controllers should focus purely on handling HTTP concerns: routing, extracting request data, and returning responses.

How to Answer This in a Technical Interview

  1. Give a two-to-three sentence definition using correct NestJS and Node.js terminology.
  2. Add one specific example drawn from a real backend scenario such as an e-commerce, fintech, or SaaS API.
  3. Mention how it compares to plain Express.js if relevant, since interviewers often probe this contrast.
  4. Close with one benefit, trade-off, or production use case.
  5. Avoid vague answers like "it just works" — interviewers filter these out immediately.

Practical Scenario

Imagine you are building a production backend for a SaaS product, similar to systems used at companies like Swiggy, Razorpay, or Freshworks. Every lesson in this NestJS course maps directly to a decision you will make while building that backend: how you structure code, how you separate concerns, how you test it, and how you scale it under real traffic.

NestJS Controllers: Architecture and Flow Diagram

Visualize a controller box labeled UsersController with five outgoing arrows, each representing a route:

[UsersController @Controller('users')]
--> GET /users (@Get())
--> GET /users/:id (@Get(':id') + @Param('id'))
--> POST /users (@Post() + @Body())
--> PUT /users/:id (@Put(':id') + @Param('id') + @Body())
--> DELETE /users/:id (@Delete(':id') + @Param('id'))

NestJS Controllers: HTTP Method Reference Table

HTTP MethodDecoratorTypical PurposeCommon Data Source
GET@Get()Retrieve a resource or list of resourcesRoute params, query strings
POST@Post()Create a new resourceRequest body
PUT@Put()Fully update an existing resourceRoute param + request body
PATCH@Patch()Partially update an existing resourceRoute param + request body
DELETE@Delete()Remove an existing resourceRoute param

NestJS Controllers: NestJS Code Example

// src/users/users.controller.ts — a full CRUD controller
import {
  Controller,
  Get,
  Post,
  Put,
  Delete,
  Param,
  Query,
  Body,
} from '@nestjs/common';
import { UsersService } from './users.service';

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  // GET /users?page=1&limit=10
  @Get()
  findAll(@Query('page') page: string, @Query('limit') limit: string) {
    return this.usersService.findAll(Number(page) || 1, Number(limit) || 10);
  }

  // GET /users/5
  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.usersService.findOne(Number(id));
  }

  // POST /users
  @Post()
  create(@Body() createUserDto: { name: string; email: string }) {
    return this.usersService.create(createUserDto);
  }

  // PUT /users/5
  @Put(':id')
  update(
    @Param('id') id: string,
    @Body() updateUserDto: { name?: string; email?: string },
  ) {
    return this.usersService.update(Number(id), updateUserDto);
  }

  // DELETE /users/5
  @Delete(':id')
  remove(@Param('id') id: string) {
    return this.usersService.remove(Number(id));
  }
}

This controller implements a complete set of CRUD routes for a users resource. Each method's parameter decorators clearly show what kind of data that route depends on: findAll reads pagination values from the query string, findOne and remove read the user's ID from the route path, create reads a full new user object from the request body, and update combines both an ID from the path and partial update data from the body. This is the exact shape almost every REST resource controller in a real NestJS application follows.

Real-World NestJS Controllers Industry Examples

  • E-commerce platforms implement nearly identical controller patterns for resources like products, categories, and orders, following the same GET/POST/PUT/DELETE structure shown here.
  • Admin dashboards commonly use query parameters extensively on list endpoints (like GET /users?search=asha&role=admin) to support filtering and searching directly through the URL.
  • Mobile app backends rely heavily on route parameters for resource-specific actions, such as GET /orders/:id/status to check a specific order's status by ID.
  • Public APIs documented with Swagger typically expose exactly this kind of predictable, resource-based controller structure so third-party developers can guess endpoint behavior easily.

NestJS Controllers Interview Questions and Answers

Q1. What is the role of a controller in NestJS?

Short answer: A controller is responsible for handling incoming HTTP requests for a specific route or group of routes, extracting relevant data from the request, delegating business logic to an injected service, and returning an appropriate response back to the client.

Detailed explanation: A controller in NestJS is declared using the @Controller() decorator, optionally passed a base path string. For example, @Controller('users') means every route defined inside this controller class is automatically prefixed with /users. Inside the controller, individual methods are decorated with HTTP method decorators like @Get(), @Post(), @Put(), and @Delete(), each optionally accepting an additional path segment appended to the controller's base path. A typical REST resource implements four or five standard operations. GET /users returns a list of all users. GET /users/:id returns a single user by their ID, using a dynamic route parameter. POST /users creates a new user, reading data from the request body. PUT /users/:id updates an existing user, combining both a route parameter and a request body. DELETE /users/:id removes a user by ID. To access these different pieces of incoming request data, NestJS provides dedicated parameter decorators used directly inside a route handler's method signature. @Param('id') extracts the value of the :id segment from the URL path. @Query('page') extracts a query string value like ?page=2. @Body() extracts the entire parsed JSON request body, or @Body('email') extracts just a specific field from it. These decorators are what make NestJS controllers so readable compared to manually parsing req.params, req.query, and req.body as you would in plain Express. Instead of digging into a generic request object, you declare exactly which piece of data your method needs directly in its signature, and NestJS handles the extraction automatically, with full TypeScript type safety on top.

Practical example: E-commerce platforms implement nearly identical controller patterns for resources like products, categories, and orders, following the same GET/POST/PUT/DELETE structure shown here.

Interview tip: @Controller('path') sets a base path prefix for all routes defined inside that controller.

Revision hook: Controllers should focus purely on handling HTTP concerns: routing, extracting request data, and returning responses.

Q2. How do you define a base path for all routes inside a controller?

Short answer: You pass a string argument to the @Controller() decorator, for example @Controller('users'), which prefixes every route defined by methods inside that controller with /users, so a method decorated with @Get(':id') becomes accessible at GET /users/:id.

Detailed explanation: This controller implements a complete set of CRUD routes for a users resource. Each method's parameter decorators clearly show what kind of data that route depends on: findAll reads pagination values from the query string, findOne and remove read the user's ID from the route path, create reads a full new user object from the request body, and update combines both an ID from the path and partial update data from the body. This is the exact shape almost every REST resource controller in a real NestJS application follows.

Practical example: Admin dashboards commonly use query parameters extensively on list endpoints (like GET /users?search=asha&role=admin) to support filtering and searching directly through the URL.

Interview tip: HTTP method decorators to remember: @Get(), @Post(), @Put(), @Patch(), @Delete().

Revision hook: Mastering @Param(), @Query(), and @Body() covers the vast majority of data extraction you will ever need in a NestJS controller.

Q3. What is the difference between @Param(), @Query(), and @Body() in NestJS?

Short answer: @Param() extracts values from dynamic segments of the URL path, such as an ID in /users/:id. @Query() extracts values from the query string after a question mark, such as ?page=2. @Body() extracts data sent in the body of the request, typically used for POST, PUT, or PATCH requests.

Detailed explanation: NestJS controllers, declared with the @Controller() decorator, are responsible for handling incoming HTTP requests and routing them to the correct logic. HTTP method decorators like @Get(), @Post(), @Put(), and @Delete() map specific verbs and paths to controller methods, forming a complete CRUD resource. Within these methods, the @Param(), @Query(), and @Body() decorators cleanly extract data from route parameters, query strings, and request bodies respectively, replacing the manual request-object parsing required in plain Express. A well-designed controller stays focused purely on these HTTP concerns, delegating all actual business logic to an injected service.

Practical example: Mobile app backends rely heavily on route parameters for resource-specific actions, such as GET /orders/:id/status to check a specific order's status by ID.

Interview tip: @Param() reads route path segments, @Query() reads query string values, @Body() reads the request body.

Revision hook: A consistent CRUD route pattern (GET, POST, PUT, DELETE) makes your API predictable and easy for other developers to consume.

Q4. How would you design routes for a full CRUD resource in NestJS?

Short answer: A typical CRUD resource uses GET for listing and retrieving (with an optional :id parameter for a single item), POST for creating a new item using the request body, PUT or PATCH for updating an existing item by ID, and DELETE for removing an item by ID, all grouped under one controller with a shared base path.

Detailed explanation: A controller in NestJS is declared using the @Controller() decorator, optionally passed a base path string. For example, @Controller('users') means every route defined inside this controller class is automatically prefixed with /users. Inside the controller, individual methods are decorated with HTTP method decorators like @Get(), @Post(), @Put(), and @Delete(), each optionally accepting an additional path segment appended to the controller's base path. A typical REST resource implements four or five standard operations. GET /users returns a list of all users. GET /users/:id returns a single user by their ID, using a dynamic route parameter. POST /users creates a new user, reading data from the request body. PUT /users/:id updates an existing user, combining both a route parameter and a request body. DELETE /users/:id removes a user by ID. To access these different pieces of incoming request data, NestJS provides dedicated parameter decorators used directly inside a route handler's method signature. @Param('id') extracts the value of the :id segment from the URL path. @Query('page') extracts a query string value like ?page=2. @Body() extracts the entire parsed JSON request body, or @Body('email') extracts just a specific field from it. These decorators are what make NestJS controllers so readable compared to manually parsing req.params, req.query, and req.body as you would in plain Express. Instead of digging into a generic request object, you declare exactly which piece of data your method needs directly in its signature, and NestJS handles the extraction automatically, with full TypeScript type safety on top.

Practical example: Public APIs documented with Swagger typically expose exactly this kind of predictable, resource-based controller structure so third-party developers can guess endpoint behavior easily.

Interview tip: Route parameters are always received as strings and must be manually converted to numbers or other types if needed.

Revision hook: Business logic belongs in services, not controllers, even when a task feels simple enough to write inline.

NestJS Controllers MCQs and Practice Questions

1. Which decorator would you use to extract the value of :id from the route /users/:id?

  1. @Query('id')
  2. @Body('id')
  3. @Param('id')
  4. @Headers('id')

Answer: C. @Param('id')

Explanation: @Param() is specifically designed to extract dynamic route path segments, such as the :id placeholder in a route like /users/:id.

Concept link: @Controller('path') sets a base path prefix for all routes defined inside that controller.

Why this matters: Controllers should focus purely on handling HTTP concerns: routing, extracting request data, and returning responses.

2. Which HTTP method decorator is most appropriate for creating a new resource?

  1. @Get()
  2. @Post()
  3. @Delete()
  4. @Options()

Answer: B. @Post()

Explanation: @Post() is conventionally used for creating new resources, typically reading the new resource's data from the request body via @Body().

Concept link: HTTP method decorators to remember: @Get(), @Post(), @Put(), @Patch(), @Delete().

Why this matters: Mastering @Param(), @Query(), and @Body() covers the vast majority of data extraction you will ever need in a NestJS controller.

3. What does @Controller('products') do?

  1. Creates a database table called products
  2. Sets the base route path for all methods in this controller to /products
  3. Registers a new module named products
  4. Deletes all existing product routes

Answer: B. Sets the base route path for all methods in this controller to /products

Explanation: The string argument passed to @Controller() sets a base path that gets prefixed to every route defined by the HTTP method decorators inside that controller class.

Concept link: @Param() reads route path segments, @Query() reads query string values, @Body() reads the request body.

Why this matters: A consistent CRUD route pattern (GET, POST, PUT, DELETE) makes your API predictable and easy for other developers to consume.

4. Which decorator extracts data from the query string, such as ?search=laptop?

  1. @Param()
  2. @Body()
  3. @Query()
  4. @Req()

Answer: C. @Query()

Explanation: @Query() is used to extract key-value pairs from the query string portion of a URL, which appears after a question mark.

Concept link: Route parameters are always received as strings and must be manually converted to numbers or other types if needed.

Why this matters: Business logic belongs in services, not controllers, even when a task feels simple enough to write inline.

Common NestJS Controllers Mistakes to Avoid

  • Confusing @Param() and @Query(), leading to undefined values when reading data from the wrong part of the request.
  • Forgetting to include the route parameter placeholder (like :id) in the HTTP method decorator's path string, causing @Param('id') to return undefined.
  • Putting business logic directly inside controller methods instead of delegating to a service, violating the separation of concerns NestJS is designed around.
  • Not converting string route parameters (which are always strings by default) to numbers or other types before using them in comparisons or database queries.

NestJS Controllers: Interview Notes and Exam Tips

  • @Controller('path') sets a base path prefix for all routes defined inside that controller.
  • HTTP method decorators to remember: @Get(), @Post(), @Put(), @Patch(), @Delete().
  • @Param() reads route path segments, @Query() reads query string values, @Body() reads the request body.
  • Route parameters are always received as strings and must be manually converted to numbers or other types if needed.

Key NestJS Controllers Takeaways

  • Controllers should focus purely on handling HTTP concerns: routing, extracting request data, and returning responses.
  • Mastering @Param(), @Query(), and @Body() covers the vast majority of data extraction you will ever need in a NestJS controller.
  • A consistent CRUD route pattern (GET, POST, PUT, DELETE) makes your API predictable and easy for other developers to consume.
  • Business logic belongs in services, not controllers, even when a task feels simple enough to write inline.

NestJS Controllers: Summary

NestJS controllers, declared with the @Controller() decorator, are responsible for handling incoming HTTP requests and routing them to the correct logic. HTTP method decorators like @Get(), @Post(), @Put(), and @Delete() map specific verbs and paths to controller methods, forming a complete CRUD resource. Within these methods, the @Param(), @Query(), and @Body() decorators cleanly extract data from route parameters, query strings, and request bodies respectively, replacing the manual request-object parsing required in plain Express. A well-designed controller stays focused purely on these HTTP concerns, delegating all actual business logic to an injected service.

Frequently Asked Questions

A controller handles HTTP-specific concerns: receiving requests, extracting data, and returning responses. A service contains the actual business logic, such as database operations or calculations, and is injected into controllers that need to use it, keeping responsibilities cleanly separated. In interviews, tie this back to: @Controller('path') sets a base path prefix for all routes defined inside that controller. In real applications, consider this example: E-commerce platforms implement nearly identical controller patterns for resources like products, categories, and orders, following the same GET/POST/PUT/DELETE structure shown here. Key revision takeaway: Controllers should focus purely on handling HTTP concerns: routing, extracting request data, and returning responses.

Yes. A single controller class commonly contains multiple methods, each decorated with a different HTTP method decorator (@Get(), @Post(), @Put(), @Delete()), together forming a complete set of operations for one resource, such as users or products. In interviews, tie this back to: HTTP method decorators to remember: @Get(), @Post(), @Put(), @Patch(), @Delete(). In real applications, consider this example: Admin dashboards commonly use query parameters extensively on list endpoints (like GET /users?search=asha&role=admin) to support filtering and searching directly through the URL. Key revision takeaway: Mastering @Param(), @Query(), and @Body() covers the vast majority of data extraction you will ever need in a NestJS controller.

URL path segments are inherently text, so NestJS's @Param() decorator always returns strings by default. If you need a number for calculations or database lookups, you must explicitly convert it, for example using Number(id), or use a validation pipe to transform it automatically. In interviews, tie this back to: @Param() reads route path segments, @Query() reads query string values, @Body() reads the request body. In real applications, consider this example: Mobile app backends rely heavily on route parameters for resource-specific actions, such as GET /orders/:id/status to check a specific order's status by ID. Key revision takeaway: A consistent CRUD route pattern (GET, POST, PUT, DELETE) makes your API predictable and easy for other developers to consume.

Calling @Query() without a specific key, for example @Query() query: any, returns an object containing all query string parameters as key-value pairs, rather than extracting just a single named parameter. In interviews, tie this back to: Route parameters are always received as strings and must be manually converted to numbers or other types if needed. In real applications, consider this example: Public APIs documented with Swagger typically expose exactly this kind of predictable, resource-based controller structure so third-party developers can guess endpoint behavior easily. Key revision takeaway: Business logic belongs in services, not controllers, even when a task feels simple enough to write inline.

No, it's optional. @Controller() with no argument means the controller's routes are relative to the application's root path. Providing a string argument, like @Controller('users'), simply adds that string as a prefix to every route defined inside the controller. In interviews, tie this back to: @Controller('path') sets a base path prefix for all routes defined inside that controller. In real applications, consider this example: E-commerce platforms implement nearly identical controller patterns for resources like products, categories, and orders, following the same GET/POST/PUT/DELETE structure shown here. Key revision takeaway: Controllers should focus purely on handling HTTP concerns: routing, extracting request data, and returning responses.

Rather than manually checking fields inside the controller method, NestJS applications typically define a DTO (Data Transfer Object) class combined with validation decorators from class-validator, and apply a ValidationPipe, which automatically validates the body before the controller method even executes. In interviews, tie this back to: HTTP method decorators to remember: @Get(), @Post(), @Put(), @Patch(), @Delete(). In real applications, consider this example: Admin dashboards commonly use query parameters extensively on list endpoints (like GET /users?search=asha&role=admin) to support filtering and searching directly through the URL. Key revision takeaway: Mastering @Param(), @Query(), and @Body() covers the vast majority of data extraction you will ever need in a NestJS controller.