Dwex Logo

Controllers

Handle HTTP requests with decorator-based routing

What are Controllers?

Controllers are responsible for handling incoming HTTP requests and returning responses. In Dwex, controllers are classes decorated with @Controller() that group related routes together.

Basic Controller

import { Controller, Get } from "@dwex/core";

@Controller("users")
export class UserController {
  @Get()
  findAll() {
    return ["Alice", "Bob", "Charlie"];
  }
}

This controller handles GET /users and returns a list of users.

Route Paths

Controller Path

The @Controller() decorator defines a base path:

@Controller("users") // Base path: /users
export class UserController {
  @Get() // Handles: GET /users
  findAll() {}

  @Get("profile") // Handles: GET /users/profile
  getProfile() {}
}

No Base Path

Controllers can have no base path:

@Controller() // No base path
export class AppController {
  @Get() // Handles: GET /
  home() {
    return "Welcome!";
  }

  @Get("health") // Handles: GET /health
  health() {
    return "OK";
  }
}

HTTP Methods

Dwex provides decorators for all standard HTTP methods:

import { Controller, Get, Post, Put, Delete, Patch } from "@dwex/core";

@Controller("posts")
export class PostController {
  @Get()
  findAll() {
    return [];
  }

  @Post()
  create() {
    return { id: 1 };
  }

  @Put(":id")
  update() {
    return { updated: true };
  }

  @Patch(":id")
  partialUpdate() {
    return { updated: true };
  }

  @Delete(":id")
  remove() {
    return { deleted: true };
  }
}

All Methods

Handle any HTTP method with @All():

import { Controller, All } from "@dwex/core";

@Controller("webhook")
export class WebhookController {
  @All()
  handleWebhook() {
    return "Webhook received";
  }
}

Route Parameters

Extract values from the URL path:

import { Controller, Get, Param } from "@dwex/core";

@Controller("users")
export class UserController {
  @Get(":id")
  findOne(@Param("id") id: string) {
    return { id, name: "John Doe" };
  }

  @Get(":userId/posts/:postId")
  findPost(@Param("userId") userId: string, @Param("postId") postId: string) {
    return { userId, postId };
  }
}

All Parameters

Get all route parameters as an object:

@Get(":userId/posts/:postId")
findPost(@Param() params: { userId: string; postId: string }) {
	return params;
}

Request Data

Query Parameters

Access query strings with @Query():

import { Controller, Get, Query } from "@dwex/core";

@Controller("search")
export class SearchController {
  @Get()
  search(@Query("q") query: string, @Query("limit") limit: string) {
    return { query, limit };
  }

  // Or get all query params
  @Get("advanced")
  advancedSearch(@Query() query: Record<string, string>) {
    return query;
  }
}

Request: GET /search?q=typescript&limit=10

Request Body

Access request body with @Body():

import { Controller, Post, Body } from "@dwex/core";

@Controller("users")
export class UserController {
  @Post()
  create(@Body() data: { name: string; email: string }) {
    return { id: 1, ...data };
  }

  // Or specific property
  @Post("register")
  register(@Body("email") email: string) {
    return { email };
  }
}

Headers

Access request headers with @Headers():

import { Controller, Get, Headers } from "@dwex/core";

@Controller("api")
export class ApiController {
  @Get("version")
  getVersion(@Headers("user-agent") userAgent: string) {
    return { userAgent };
  }

  // Or all headers
  @Get("info")
  getInfo(@Headers() headers: Record<string, string>) {
    return { headers };
  }
}

Cookies

Access cookies with @Cookies():

import { Controller, Get, Cookies } from "@dwex/core";

@Controller("session")
export class SessionController {
  @Get()
  getSession(@Cookies("sessionId") sessionId: string) {
    return { sessionId };
  }
}

Request & Response Objects

Access the raw request/response:

import { Controller, Get, Req, Res } from "@dwex/core";

@Controller("raw")
export class RawController {
  @Get()
  handle(@Req() request: Request, @Res() response: Response) {
    return { url: request.url, method: request.method };
  }
}

Responses

Automatic JSON

Return objects or arrays, Dwex automatically serializes to JSON:

@Get()
findAll() {
	return [{ id: 1, name: 'Alice' }];
}

Response:

[{ "id": 1, "name": "Alice" }]

Status Codes

Throw exceptions to return error status codes:

import { Controller, Get, Param, NotFoundException } from "@dwex/core";

@Controller("users")
export class UserController {
  @Get(":id")
  findOne(@Param("id") id: string) {
    const user = this.users.find((u) => u.id === id);

    if (!user) {
      throw new NotFoundException("User not found");
    }

    return user;
  }
}

Custom Response

Use @Res() for full control:

@Get('download')
download(@Res() response: Response) {
	response.setHeader('Content-Type', 'application/pdf');
	response.send(pdfBuffer);
}

Dependency Injection

Controllers can inject services via constructor:

import { Controller, Get, Injectable } from "@dwex/core";

@Injectable()
class UserService {
  findAll() {
    return ["Alice", "Bob"];
  }
}

@Controller("users")
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Get()
  findAll() {
    return this.userService.findAll();
  }
}

Register both in a module:

@Module({
  controllers: [UserController],
  providers: [UserService],
})
export class UserModule {}

Async Handlers

Route handlers can be async:

@Controller("users")
export class UserController {
  constructor(private readonly db: DatabaseService) {}

  @Get()
  async findAll() {
    return await this.db.users.findMany();
  }

  @Post()
  async create(@Body() data: CreateUserDto) {
    return await this.db.users.create(data);
  }
}

Request IP & Host

Get client IP and hostname:

import { Controller, Get, Ip, Host } from "@dwex/core";

@Controller("info")
export class InfoController {
  @Get("ip")
  getIp(@Ip() ip: string) {
    return { ip };
  }

  @Get("host")
  getHost(@Host() host: string) {
    return { host };
  }
}

Best Practices

1. Keep Controllers Thin

Controllers should delegate business logic to services:

// Good
@Controller("users")
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Get()
  findAll() {
    return this.userService.findAll();
  }
}

// Avoid
@Controller("users")
export class UserController {
  @Get()
  findAll() {
    // Complex logic in controller
    const users = database.query("SELECT * FROM users");
    return users.map((u) => transformUser(u));
  }
}

2. Use DTOs

Define types for request data:

interface CreateUserDto {
	email: string;
	name: string;
	password: string;
}

@Post()
create(@Body() data: CreateUserDto) {
	return this.userService.create(data);
}

3. Consistent Naming

Follow REST conventions:

  • findAll() - GET collection
  • findOne() - GET single item
  • create() - POST
  • update() - PUT
  • remove() - DELETE

4. Handle Errors

Always validate and handle errors:

@Get(':id')
async findOne(@Param('id') id: string) {
	const user = await this.userService.findOne(id);

	if (!user) {
		throw new NotFoundException(`User #${id} not found`);
	}

	return user;
}

Next Steps