Criando um Controller com o Igniter.js no SaaS Boilerplate
Controllers são uma parte fundamental do framework Igniter. Eles lidam com requisições HTTP recebidas, processam dados e retornam respostas apropriadas. Os controllers no Igniter são projetados para ter segurança de tipos, serem modulares e fáceis de testar.
Criando um Controller
Para criar um controller no Igniter, você usa a função igniter.controller()
. Aqui está um exemplo básico:
// src/features/user/controllers/user.controller.ts import { igniter } from '@/igniter' import { userService } from '../services/user.service' export const userController = igniter.controller({ path: '/users', actions: { list: igniter.query({ path: '/', handler: async (ctx) => { const users = await userService.findAll() return ctx.response.ok(users) } }), get: igniter.query({ path: '/:id', handler: async (ctx) => { const { id } = ctx.params const user = await userService.findById(id) if (!user) { return ctx.response.notFound('User not found') } return ctx.response.ok(user) } }), create: igniter.mutation({ path: '/', method: 'POST', handler: async (ctx) => { const data = ctx.body const user = await userService.create(data) return ctx.response.created(user) } }), update: igniter.mutation({ path: '/:id', method: 'PUT', handler: async (ctx) => { const { id } = ctx.params const data = ctx.body const user = await userService.update(id, data) if (!user) { return ctx.response.notFound('User not found') } return ctx.response.ok(user) } }), delete: igniter.mutation({ path: '/:id', method: 'DELETE', handler: async (ctx) => { const { id } = ctx.params await userService.delete(id) return ctx.response.noContent() } }) } })
A configuração do controller inclui:
path
: O caminho base para todas as ações no controller (por exemplo,/users
)actions
: Um objeto contendo todas as ações (endpoints) para o controller
Ações do controller
Cada ação em um controller representa um endpoint. O Igniter fornece dois tipos de ações:
Queries
Queries são usadas para operações de leitura (requisições GET). Elas não modificam dados e são seguras para chamar múltiplas vezes.
list: igniter.query({ path: '/', handler: async (ctx) => { // Handle GET request } })
Mutations
Mutations são usadas para operações de escrita (requisições POST, PUT, DELETE). Elas modificam dados e devem ser idempotentes quando possível.
create: igniter.mutation({ path: '/', method: 'POST', handler: async (ctx) => { // Handle POST request } })
Contexto da Requisição
Cada ação do controller recebe um objeto de contexto (ctx
) que fornece acesso aos dados da requisição e utilitários para gerar respostas:
handler: async (ctx) => { // Dados da requisição const params = ctx.params // Parâmetros de URL const query = ctx.query // Parâmetros de query string const body = ctx.body // Corpo da requisição const headers = ctx.headers // Cabeçalhos da requisição // Utilitários de resposta return ctx.response.ok(data) // Resposta 200 OK // Outros métodos de resposta: created, noContent, badRequest, unauthorized, forbidden, notFound, etc. }
Usando Procedimentos com controllers
Procedimentos são funções de middleware que podem ser aplicadas às ações do controller. Eles permitem adicionar preocupações transversais como autenticação, validação e registro de logs.
import { igniter } from '@/igniter' import { authProcedure } from '@/procedures/auth.procedure' import { validateUserProcedure } from '@/procedures/validate-user.procedure' export const userController = igniter.controller({ path: '/users', actions: { create: igniter.mutation({ path: '/', method: 'POST', use: [ authProcedure(), // Authenticate the request validateUserProcedure() // Validate the user data ], handler: async (ctx) => { // Handle the request } }) } })
Manipulando Respostas
O Igniter fornece um conjunto de utilitários de resposta para facilitar o retorno de respostas HTTP padronizadas:
// Respostas de sucesso ctx.response.ok(data) // 200 OK ctx.response.created(data) // 201 Created ctx.response.accepted() // 202 Accepted ctx.response.noContent() // 204 No Content // Respostas de erro do cliente ctx.response.badRequest(message) // 400 Bad Request ctx.response.unauthorized(message) // 401 Unauthorized ctx.response.forbidden(message) // 403 Forbidden ctx.response.notFound(message) // 404 Not Found ctx.response.methodNotAllowed(message) // 405 Method Not Allowed ctx.response.conflict(message) // 409 Conflict // Respostas de erro do servidor ctx.response.internalServerError(message) // 500 Internal Server Error ctx.response.notImplemented(message) // 501 Not Implemented ctx.response.badGateway(message) // 502 Bad Gateway ctx.response.serviceUnavailable(message) // 503 Service Unavailable
Segurança de Tipos
Um dos principais benefícios dos controllers Igniter é a segurança de tipos. O framework fornece inferência completa de tipos para suas ações de controller, garantindo que seu código cliente esteja sempre sincronizado com seu código de servidor.
// Define input and output types for your controller actions import { z } from 'zod' const createUserSchema = z.object({ name: z.string(), email: z.string().email(), password: z.string().min(8) }) type CreateUserInput = z.infer<typeof createUserSchema> type User = { id: string; name: string; email: string } export const userController = igniter.controller({ path: '/users', actions: { create: igniter.mutation<CreateUserInput, User>({ path: '/', method: 'POST', handler: async (ctx) => { // ctx.body is typed as CreateUserInput const user = await userService.create(ctx.body) // Return value is typed as User return ctx.response.created(user) } }) } })
Fábricas de controllers
Você pode criar fábricas de controllers para gerar controllers com comportamento semelhante:
function createCrudController<T>(options: { path: string, service: CrudService<T> }) { return igniter.controller({ path: options.path, actions: { list: igniter.query({ path: '/', handler: async (ctx) => { const items = await options.service.findAll() return ctx.response.ok(items) } }), get: igniter.query({ path: '/:id', handler: async (ctx) => { const { id } = ctx.params const item = await options.service.findById(id) if (!item) { return ctx.response.notFound('Item not found') } return ctx.response.ok(item) } }), // Add other CRUD actions... } }) } // Usage export const userController = createCrudController({ path: '/users', service: userService })
Melhores Práticas
-
Mantenha os controllers Focados: Os controllers devem se concentrar em lidar com requisições e respostas HTTP. A lógica de negócios deve ser delegada aos serviços.
-
Use Procedimentos para Preocupações Transversais: Use procedimentos para autenticação, validação, registro de logs e outras preocupações transversais.
-
Formatos de Resposta Consistentes: Use os utilitários de resposta integrados para garantir formatos de resposta consistentes em toda a sua API.
-
Aproveite a Segurança de Tipos: Use o sistema de tipos do TypeScript para garantir que sua API seja segura em termos de tipos.
-
Organize por Recurso: Agrupe controllers por recurso ou domínio, em vez de por função técnica.
-
Valide a Entrada: Sempre valide os dados de entrada antes de processá-los.
-
Trate Erros com Elegância: Implemente tratamento adequado de erros para fornecer mensagens de erro significativas aos clientes.
-
Use Injeção de Dependência: Injete serviços e outras dependências em seus controllers para torná-los mais fáceis de testar.
-
Escreva Testes: Escreva testes unitários e de integração para seus controllers para garantir que funcionem conforme o esperado.
Acelere seu desenvolvimento
Construa aplicações SaaS completas em minutos com nosso boilerplate moderno. Autenticação, pagamentos, gerenciamento de usuários e muito mais!
Conheça o SaaS Boilerplate