Published on 2025-03-08

Creating a Procedure

Published on

2025-03-08

Procedures

Procedures in Igniter are powerful middleware functions that allow you to implement cross-cutting concerns like authentication, validation, error handling, and logging. They can be applied to individual controller actions or globally to your router.

Creating a Procedure

To create a procedure in Igniter, you use the igniter.procedure() function. Here's a basic example:

typescript
// src/procedures/auth.procedure.ts
import { igniter } from '@/igniter'
import { verifyToken } from '@/utils/jwt'

export const auth = igniter.procedure({
  handler: async (input, ctx) => {
    // Get the authorization header
    const authHeader = ctx.headers.authorization
    
    if (!authHeader || !authHeader.startsWith('Bearer ')) {
      return ctx.response.unauthorized('Missing or invalid authorization token')
    }
    
    const token = authHeader.split(' ')[1]
    
    try {
      // Verify the token
      const user = await verifyToken(token)
      
      // Add the user to the context
      ctx.user = user
      
      // Continue to the next middleware or handler
      return ctx.next()
    } catch (error) {
      return ctx.response.unauthorized('Invalid token')
    }
  }
})

The procedure configuration includes:

  • handler: A function that receives the input and context, and returns a response or calls ctx.next() to continue to the next middleware or handler

Using Procedures with Controllers

Procedures can be applied to individual controller actions:

typescript
// src/features/user/controllers/user.controller.ts
import { igniter } from '@/igniter'
import { auth } from '@/procedures/auth.procedure'

export const userController = igniter.controller({
  path: '/users',
  actions: {
    list: igniter.query({
      path: '/',
      use: [auth()], // Apply the auth procedure
      handler: async (ctx) => {
        // This handler will only be called if the auth procedure passes
        const users = await ctx.providers.database.user.findMany()
        return ctx.response.ok(users)
      }
    })
  }
})

Using Procedures with Routers (Coming Soon)

Note: This feature is currently in development and will be available in a future release.

In the future, procedures will be able to be applied globally to your router:

typescript
// src/igniter.router.ts
import { igniter } from '@/igniter'
import { userController } from '@/features/user/controllers/user.controller'
import { logger } from '@/procedures/logger.procedure'
import { errorHandler } from '@/procedures/error-handler.procedure'

export const AppRouter = igniter.router({
  baseURL: 'https://localhost:3000',
  basePATH: '/api/v1',
  controllers: {
    users: userController
  },
  use: [
    logger(), // Applied to all requests (coming soon)
    errorHandler() // Applied to all requests (coming soon)
  ]
})

Common Procedure Patterns

Note: Igniter.js has built-in support for input validation in mutations and queries. The examples below focus on other common use cases for procedures.

Error Handling

typescript
// src/procedures/error-handler.procedure.ts
import { igniter } from '@/igniter'

export const errorHandler = igniter.procedure({
  handler: async (_, ctx) => {
    try {
      // Continue to the next middleware or handler
      return await ctx.next()
    } catch (error) {
      // Log the error
      console.error('API Error:', error)
      
      // Return an appropriate error response
      if (error.name === 'ValidationError') {
        return ctx.response.badRequest(error.message)
      }
      
      if (error.name === 'NotFoundError') {
        return ctx.response.notFound(error.message)
      }
      
      // Default error response
      return ctx.response.internalServerError('An unexpected error occurred')
    }
  }
})

Logging

typescript
// src/procedures/logger.procedure.ts
import { igniter } from '@/igniter'

export const logger = igniter.procedure({
  handler: async (_, ctx) => {
    const start = Date.now()
    
    // Log the request
    console.log(`${ctx.method} ${ctx.path} - Request received`)
    
    // Continue to the next middleware or handler
    const response = await ctx.next()
    
    // Log the response
    const duration = Date.now() - start
    console.log(`${ctx.method} ${ctx.path} - Response sent (${response.status}) in ${duration}ms`)
    
    return response
  }
})

Chaining Procedures

Procedures can be chained together to create a pipeline of middleware:

typescript
export const userController = igniter.controller({
  path: '/users',
  actions: {
    create: igniter.mutation({
      path: '/',
      method: 'POST',
      use: [
        logger(), // First procedure in the chain
        auth(), // Second procedure in the chain
      ],
      handler: async (ctx) => {
        // Handler is only called if all procedures pass
      }
    })
  }
})

Best Practices

  1. Keep Procedures Focused: Each procedure should focus on a single concern.

  2. Use Procedures for Cross-Cutting Concerns: Use procedures for authentication, validation, logging, and other cross-cutting concerns.

  3. Chain Procedures in a Logical Order: Order your procedures logically, with general-purpose procedures (like logging) first and more specific ones (like validation) later.

  4. Handle Errors Gracefully: Implement proper error handling in your procedures to provide meaningful error messages to clients.

  5. Share Context Between Procedures: Use the context object to share data between procedures and handlers.

  6. Make Procedures Reusable: Design your procedures to be reusable across different controllers and actions.

AD

Quick Tip

Always implement proper error handling and reconnection logic in your SSE clients to ensure a robust user experience.

You might also like