Criando um Controller

Publicado em

08/03/2025

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

  1. 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.

  2. Use Procedimentos para Preocupações Transversais: Use procedimentos para autenticação, validação, registro de logs e outras preocupações transversais.

  3. Formatos de Resposta Consistentes: Use os utilitários de resposta integrados para garantir formatos de resposta consistentes em toda a sua API.

  4. Aproveite a Segurança de Tipos: Use o sistema de tipos do TypeScript para garantir que sua API seja segura em termos de tipos.

  5. Organize por Recurso: Agrupe controllers por recurso ou domínio, em vez de por função técnica.

  6. Valide a Entrada: Sempre valide os dados de entrada antes de processá-los.

  7. Trate Erros com Elegância: Implemente tratamento adequado de erros para fornecer mensagens de erro significativas aos clientes.

  8. Use Injeção de Dependência: Injete serviços e outras dependências em seus controllers para torná-los mais fáceis de testar.

  9. Escreva Testes: Escreva testes unitários e de integração para seus controllers para garantir que funcionem conforme o esperado.

SaaS Boilerplate

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

Você também pode gostar