Procedures in Igniter.js

Procedures are one of the most powerful and flexible features in Igniter.js, providing a sophisticated middleware system that enables you to create reusable, composable, and type-safe request processing logic. This comprehensive guide will take you through everything you need to know about procedures, from basic concepts to advanced patterns.

What Are Procedures?

In Igniter.js, a procedure is a reusable piece of middleware that can be applied to actions or at the builder level. Think of procedures as building blocks that encapsulate common functionality like authentication, logging, rate limiting, input validation, or any custom business logic you need to run before your main handler executes.

Procedures operate within the request lifecycle, executing before your action handlers and having the ability to:

  • Validate and transform input data
  • Authenticate and authorize requests
  • Log request information
  • Implement rate limiting
  • Add custom context data
  • Perform early returns (like redirects or error responses)
  • Access the global application context (database connections, services, etc.)
  • Chain with other procedures for complex workflows

Core Concepts

Procedure Context

Every procedure receives an IgniterProcedureContext object that contains all the information about the current request:

Understanding the Procedure Context: A Properties Breakdown

To understand the IgniterProcedureContext object that every procedure receives, you can expand the section below to see a detailed breakdown of its properties.

The context is the primary way procedures interact with the request lifecycle, providing a clean and type-safe interface for middleware operations.

Creating Your First Procedure

Let's start with a simple example using the igniter.procedure function. We'll create a request logging procedure:

Code
import { igniter } from '@/igniter';

// Create a simple logging procedure
export const requestLogger = igniter.procedure({
  name: 'RequestLogger',
  handler: async ({ request, context, response, next }) => {
    const startTime = Date.now();
    
    // Use logger from global context if available
    const logger = context.logger || console;
    logger.log(`[${new Date().toISOString()}] ${request.method} ${request.url}`);
    
    // Continue to the next middleware or handler
    const result = await next();
    
    const duration = Date.now() - startTime;
    logger.log(`Request completed in ${duration}ms`);
    
    return result;
  },
});

This procedure:

  1. Logs the incoming request with timestamp, method, and URL
  2. Calls next() to continue the middleware chain
  3. Logs the completion time after the request is processed
  4. Returns the result from the next middleware or handler

Using the Procedure

Once created, you can use this procedure in your controllers:

Action-Level Procedures

Code
export const userController = igniter.controller({
  name: 'UserController',
  path: '/users',
  actions: {
    getUser: igniter.query({
      path: '/:id',
      // Apply the procedure only to this action
      use: [requestLogger],
      handler: ({ request, response }) => {
        return response.success({ user: { id: request.params.id } });
      },
    }),
  },
});

Best Practices

Following established best practices when creating procedures ensures your code remains maintainable, reusable, and type-safe. These guidelines will help you build robust middleware that integrates seamlessly with the Igniter.js ecosystem.

Proper procedure design not only improves code quality but also enhances developer experience by making your APIs more predictable and easier to debug. Each practice below addresses common challenges developers face when building production applications.

Conclusion

Procedures are a fundamental building block of Igniter.js applications, providing a powerful and flexible way to implement cross-cutting concerns in your API using the igniter.procedure function. By understanding how to create, compose, and use procedures effectively, you can build more maintainable, reusable, and type-safe applications.

Key Takeaways