Builder

Learn how to configure and initialize your Igniter.js application using the powerful builder pattern with full type safety and IntelliSense.

Overview

The Igniter Builder is the foundation of your application. It provides a fluent, chainable API for configuring context, adapters, middleware, and creating fully typed API components.

import { Igniter } from '@igniter-js/core';

const igniter = Igniter
  .context(createIgniterAppContext())
  .config({ baseURL: 'https://api.example.com' })
  .store(redisAdapter)
  .jobs(bullmqAdapter)
  .create();

Immutable Pattern

The builder uses an immutable chaining pattern. Each method returns a new instance with updated configuration, ensuring type safety throughout the entire chain.


Core Concepts

The Builder Pattern

Igniter.js uses the builder pattern to provide:

  • Progressive Type Enhancement - Types become more specific as you configure
  • IntelliSense at Every Step - Full autocomplete for all methods
  • Compile-Time Validation - TypeScript catches configuration errors early
  • Flexible Configuration - Add only what your application needs

Configuration Flow


Builder Methods

.context<TContext>()

Defines the application-wide context type that will be available in all actions, procedures, and middleware.

Static Context

Use a static object when your context doesn't depend on the request:

interface AppContext {
  db: Database;
  config: AppConfig;
  services: {
    email: EmailService;
    storage: StorageService;
  };
}

// Create context factory function
export function createIgniterAppContext(): AppContext {
  return {
    db: new Database(),
    config: loadAppConfig(),
    services: {
      email: new EmailService(),
      storage: new StorageService()
    }
  };
}

const igniter = Igniter
  .context(createIgniterAppContext())
  .create();

Dynamic Context (Callback)

Use a callback function when context depends on the incoming request:

type ContextCallback = (req: Request) => Promise<AppContext> | AppContext;

const igniter = Igniter
  .context(async (req: Request) => {
    const session = await getSession(req);
    const user = session ? await db.users.findById(session.userId) : null;
    
    return {
      db,
      user,
      session,
      requestId: crypto.randomUUID(),
      ip: req.headers.get('x-forwarded-for') || 'unknown'
    };
  })
  .create();

Type Inference

When using a callback, TypeScript automatically infers the context type from the return value. No manual type annotations needed!

Accessing Context in Actions

const userController = igniter.controller({
  path: '/users',
  actions: {
    me: igniter.query({
      path: '/me',
      handler: async ({ context, response }) => {
        // ✅ context.user is fully typed
        if (!context.user) {
          return response.unauthorized({ message: 'Not authenticated' });
        }
        
        return response.success({ user: context.user });
      }
    })
  }
});

.config()

Configures router-level settings like base URL and path prefix.

const igniter = Igniter
  .context(createIgniterAppContext())
  .config({
    baseURL: 'https://api.example.com',
    basePATH: '/api/v1'
  })
  .create();

Configuration Options

Prop

Type

Usage Example

// With this config:
.config({
  baseURL: 'https://api.example.com',
  basePATH: '/api/v1'
})

// A controller with path '/users'
igniter.controller({ path: '/users', ... })

// Creates routes at:
// https://api.example.com/api/v1/users

.store(adapter)

Configures a store adapter for caching, key-value storage, and pub/sub messaging.

import { createRedisStoreAdapter } from '@igniter-js/adapter-redis';

const redisStore = createRedisStoreAdapter({
  host: 'localhost',
  port: 6379,
  password: process.env.REDIS_PASSWORD
});

const igniter = Igniter
  .context(createIgniterAppContext())
  .store(redisStore)
  .create();

Using the Store

const userController = igniter.controller({
  actions: {
    getCached: igniter.query({
      handler: async ({ context, response }) => {
        // ✅ Store is automatically available
        const cached = await igniter.store.get('users:list');
        
        if (cached) {
          return response.success({ users: JSON.parse(cached) });
        }
        
        const users = await context.db.users.findMany();
        await igniter.store.set('users:list', JSON.stringify(users), { ttl: 300 });
        
        return response.success({ users });
      }
    })
  }
});

Adding a store adapter also automatically enables real-time capabilities via the .realtime service.


.logger(adapter)

Configures a logger adapter for structured logging.

import { createConsoleLogger } from '@igniter-js/logger-console';

const logger = createConsoleLogger({
  level: 'info',
  colorize: true,
  context: { 
    service: 'api', 
    version: '1.0.0' 
  }
});

const igniter = Igniter
  .context(createIgniterAppContext())
  .logger(logger)
  .create();

Using the Logger

const userController = igniter.controller({
  actions: {
    create: igniter.mutation({
      handler: async ({ request, response }) => {
        igniter.logger.info('Creating user', { 
          email: request.body.email 
        });
        
        try {
          const user = await db.users.create(request.body);
          igniter.logger.info('User created', { userId: user.id });
          return response.created({ user });
        } catch (error) {
          igniter.logger.error('Failed to create user', { error });
          return response.error({ message: 'Creation failed' });
        }
      }
    })
  }
});

.jobs(adapter)

Configures a job queue adapter for background processing and scheduled tasks.

import { createBullMQAdapter } from '@igniter-js/adapter-bullmq';

const jobsAdapter = createBullMQAdapter({
  store: redisStore,
  defaultJobOptions: {
    removeOnComplete: 10,
    removeOnFail: 50,
    attempts: 3,
    backoff: {
      type: 'exponential',
      delay: 1000
    }
  }
});

const igniter = Igniter
  .context(createIgniterAppContext())
  .store(redisStore)
  .jobs(jobsAdapter)
  .create();

Defining Jobs

// Define your jobs with full type safety
const jobs = jobsAdapter.create({
  sendEmail: {
    handler: async (payload: { to: string; subject: string; body: string }) => {
      await emailService.send(payload);
    }
  },
  processOrder: {
    handler: async (payload: { orderId: string }) => {
      const order = await db.orders.findById(payload.orderId);
      await processOrderLogic(order);
    }
  }
});

Using Jobs in Actions

const orderController = igniter.controller({
  actions: {
    create: igniter.mutation({
      handler: async ({ request, response }) => {
        const order = await db.orders.create(request.body);
        
        // ✅ Queue a background job
        await igniter.jobs.processOrder.add({ orderId: order.id });
        
        return response.created({ order });
      }
    })
  }
});

.telemetry(provider)

Configures a telemetry provider for distributed tracing, metrics, and observability.

import { createOpenTelemetryProvider } from '@igniter-js/adapter-opentelemetry';

const telemetry = createOpenTelemetryProvider({
  serviceName: 'igniter-api',
  serviceVersion: '1.0.0',
  tracing: {
    enabled: true,
    exporter: 'jaeger',
    endpoint: 'http://localhost:14268/api/traces'
  },
  metrics: {
    enabled: true,
    exporter: 'prometheus',
    port: 9464
  }
});

const igniter = Igniter
  .context(createIgniterAppContext())
  .telemetry(telemetry)
  .create();

Automatic Instrumentation

Telemetry automatically traces:

  • ✅ HTTP requests and responses
  • ✅ Action execution times
  • ✅ Procedure (middleware) execution
  • ✅ Database queries (if using supported ORMs)
  • ✅ External API calls

Manual Spans

const userController = igniter.controller({
  actions: {
    create: igniter.mutation({
      handler: async ({ request, response }) => {
        // Create custom span for specific operation
        const span = igniter.telemetry.startSpan('validate-user-data');
        
        try {
          await validateUserData(request.body);
          span.end();
        } catch (error) {
          span.recordException(error);
          span.end();
          throw error;
        }
        
        const user = await db.users.create(request.body);
        return response.created({ user });
      }
    })
  }
});

.plugins(pluginsRecord)

Registers plugins to extend functionality with reusable, self-contained modules.

import { createAuditPlugin } from './plugins/audit';
import { createAuthPlugin } from './plugins/auth';
import { createEmailPlugin } from './plugins/email';

const igniter = Igniter
  .context(createIgniterAppContext())
  .plugins({
    audit: createAuditPlugin(),
    auth: createAuthPlugin(),
    email: createEmailPlugin()
  })
  .create();

Using Plugins in Actions

const userController = igniter.controller({
  actions: {
    create: igniter.mutation({
      handler: async ({ request, context, plugins, response }) => {
        // ✅ Full type safety for plugin actions
        const isValid = await plugins.auth.actions.validateToken({
          token: request.headers.get('authorization')
        });
        
        if (!isValid) {
          return response.unauthorized({ message: 'Invalid token' });
        }
        
        const user = await context.db.users.create(request.body);
        
        // ✅ Log to audit trail
        await plugins.audit.actions.create({
          action: 'user:created',
          userId: user.id,
          metadata: { email: user.email }
        });
        
        // ✅ Send welcome email
        await plugins.email.actions.sendWelcome({
          to: user.email,
          name: user.name
        });
        
        return response.created({ user });
      }
    })
  }
});

Plugin Type Safety

All plugin actions are fully typed! IntelliSense shows available actions, required parameters, and return types.


.docs(config)

Configures API documentation and interactive playground using OpenAPI specification.

const igniter = Igniter
  .context(createIgniterAppContext())
  .docs({
    openapi: '3.1.0',
    info: {
      title: 'My API',
      version: '1.0.0',
      description: 'A fully type-safe API built with Igniter.js'
    },
    servers: [
      { url: 'https://api.example.com', description: 'Production' },
      { url: 'http://localhost:3000', description: 'Development' }
    ],
    playground: {
      enabled: true,
      route: '/docs',
      security: async (req) => {
        // Protect playground in production
        return req.headers.get('x-admin-key') === process.env.ADMIN_KEY;
      }
    }
  })
  .create();

Documentation Options

Prop

Type


.create()

Finalizes the builder and returns the fully configured Igniter instance with all methods available.

const igniter = Igniter
  .context(createIgniterAppContext())
  .config({ baseURL: 'https://api.example.com' })
  .store(redisStore)
  .jobs(jobsAdapter)
  .create(); // ← Returns configured instance

// Now you can use:
igniter.query(...)
igniter.mutation(...)
igniter.controller(...)
igniter.router(...)
igniter.procedure(...)

// And access adapters:
igniter.store
igniter.logger
igniter.jobs
igniter.telemetry
igniter.realtime
igniter.plugins

Must Call .create()

You must call .create() to finalize the builder. Without it, you won't have access to query, mutation, controller, and other methods.


Complete Examples

Minimal Setup

Perfect for getting started or simple APIs:

import { Igniter } from '@igniter-js/core';

interface AppContext {
  db: Database;
}

const igniter = Igniter
  .context(createIgniterAppContext())
  .create();

// Ready to create actions!

Production Setup

Full-featured configuration for production applications:

import { Igniter } from '@igniter-js/core';
import { createRedisStoreAdapter } from '@igniter-js/adapter-redis';
import { createBullMQAdapter } from '@igniter-js/adapter-bullmq';
import { createOpenTelemetryProvider } from '@igniter-js/adapter-opentelemetry';

// 1. Setup adapters
const redisStore = createRedisStoreAdapter({
  host: process.env.REDIS_HOST,
  port: Number(process.env.REDIS_PORT),
  password: process.env.REDIS_PASSWORD
});

const jobsAdapter = createBullMQAdapter({
  store: redisStore,
  defaultJobOptions: {
    removeOnComplete: 10,
    removeOnFail: 50,
    attempts: 3
  }
});

const telemetry = createOpenTelemetryProvider({
  serviceName: 'my-api',
  serviceVersion: '1.0.0'
});

// 2. Create jobs
const jobs = jobsAdapter.create({
  sendEmail: { handler: async (payload) => { /* ... */ } },
  processOrder: { handler: async (payload) => { /* ... */ } }
});

// 3. Configure Igniter
const igniter = Igniter
  .context(async (req: Request) => {
    const session = await getSession(req);
    return {
      db: database,
      user: session?.user || null,
      requestId: crypto.randomUUID()
    };
  })
  .config({
    baseURL: process.env.API_URL,
    basePATH: '/api/v1'
  })
  .store(redisStore)
  .jobs(jobs)
  .telemetry(telemetry)
  .docs({
    openapi: '3.1.0',
    info: {
      title: 'My Production API',
      version: '1.0.0'
    },
    playground: {
      enabled: process.env.NODE_ENV === 'development',
      route: '/docs'
    }
  })
  .create();

export { igniter };

With Plugins

Extending functionality with modular plugins:

import { Igniter } from '@igniter-js/core';
import { auditPlugin } from './plugins/audit';
import { authPlugin } from './plugins/auth';
import { emailPlugin } from './plugins/email';

const igniter = Igniter
  .context(createIgniterAppContext())
  .store(redisStore)
  .plugins({
    audit: auditPlugin,
    auth: authPlugin,
    email: emailPlugin
  })
  .create();

// Use plugins in actions
const userController = igniter.controller({
  actions: {
    create: igniter.mutation({
      handler: async ({ plugins, response }) => {
        await plugins.audit.actions.log({ event: 'user:create' });
        await plugins.email.actions.send({ to: 'admin@example.com' });
        return response.success({ message: 'Done' });
      }
    })
  }
});

Type Inference

The builder progressively enhances types as you configure:

// Step 1: No context yet
const step1 = Igniter;
// Type: IgniterBuilder<{}, {}, ...>

// Step 2: Context added
const step2 = Igniter.context<{ db: Database }>();
// Type: IgniterBuilder<{ db: Database }, {}, ...>

// Step 3: Store added
const step3 = step2.store(redisStore);
// Type: IgniterBuilder<{ db: Database }, {}, RedisStoreAdapter, ...>

// Step 4: Created
const step4 = step3.create();
// Now has: .query(), .mutation(), .controller(), etc.
// Plus: .store, .logger, .jobs, .telemetry, .realtime, .plugins

Full IntelliSense

At every step, TypeScript provides complete autocomplete and type checking. You can't make invalid configurations!


Adapter Access

After calling .create(), all configured adapters are available directly on the Igniter instance:

const igniter = Igniter
  .context(createIgniterAppContext())
  .store(redisStore)
  .jobs(jobsAdapter)
  .telemetry(telemetry)
  .create();

// Access adapters anywhere:
igniter.store.get('key');
igniter.store.set('key', 'value');
igniter.store.subscribe('channel', callback);

igniter.jobs.sendEmail.add({ to: 'user@example.com' });
igniter.jobs.processOrder.addBulk([{ orderId: '1' }, { orderId: '2' }]);

igniter.telemetry.startSpan('operation-name');

igniter.realtime.broadcast('channel', { data: 'value' });

Best Practices

1. Single Igniter Instance

Create one Igniter instance per application and export it:

// src/igniter.ts
export const igniter = Igniter
  .context(createIgniterAppContext())
  .config({ baseURL: process.env.API_URL })
  .create();
// src/features/users/users.controller.ts
import { igniter } from '@/igniter';

export const userController = igniter.controller({ /* ... */ });

2. Environment-Based Configuration

Use environment variables for different environments:

const igniter = Igniter
  .context(createIgniterAppContext())
  .config({
    baseURL: process.env.NODE_ENV === 'production' 
      ? 'https://api.example.com' 
      : 'http://localhost:3000',
    basePATH: '/api/v1'
  })
  .docs({
    playground: {
      enabled: process.env.NODE_ENV === 'development'
    }
  })
  .create();

3. Reusable Context Functions

Extract context logic into reusable functions:

async function createRequestContext(req: Request): Promise<AppContext> {
  const session = await getSession(req);
  const user = session ? await db.users.findById(session.userId) : null;
  
  return {
    db,
    user,
    requestId: crypto.randomUUID(),
    ip: req.headers.get('x-forwarded-for') || 'unknown'
  };
}

const igniter = Igniter
  .context(createRequestContext)
  .create();

4. Conditional Adapters

Only add adapters when needed:

let builder = Igniter.context(createIgniterAppContext());

// Only add telemetry in production
if (process.env.NODE_ENV === 'production') {
  builder = builder.telemetry(telemetryProvider);
}

// Only add jobs if Redis is available
if (process.env.REDIS_URL) {
  builder = builder.store(redisStore).jobs(jobsAdapter);
}

const igniter = builder.create();

Next Steps

Now that you understand the Builder, learn how to create APIs: