Published on 2025-03-08
Creating a Procedure
Published on
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:
// 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:
// 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:
// 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
// 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
// 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:
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
-
Keep Procedures Focused: Each procedure should focus on a single concern.
-
Use Procedures for Cross-Cutting Concerns: Use procedures for authentication, validation, logging, and other cross-cutting concerns.
-
Chain Procedures in a Logical Order: Order your procedures logically, with general-purpose procedures (like logging) first and more specific ones (like validation) later.
-
Handle Errors Gracefully: Implement proper error handling in your procedures to provide meaningful error messages to clients.
-
Share Context Between Procedures: Use the context object to share data between procedures and handlers.
-
Make Procedures Reusable: Design your procedures to be reusable across different controllers and actions.
Quick Tip
Always implement proper error handling and reconnection logic in your SSE clients to ensure a robust user experience.