Core Concepts

Configuration

A guide to the config-driven approach in Raptor

Introduction

Raptor uses a config-driven approach to package setup. Each first-party package accepts an optional configuration object, allowing you to customise behaviour, register custom logic, and keep your bootstrap file clean and consistent.

Configuration files are plain TypeScript objects, meaning you get full type safety, IDE autocompletion, and the flexibility to assemble configs however you like.

Bootstrap File

The recommended way to bootstrap a Raptor application is to import each package's helper function alongside its configuration file:

import { Kernel } from "@raptor/kernel";
 
import router from "@raptor/router";
import errorHandler from "@raptor/error";
import validator from "@raptor/validator";
 
import errorConfig from "../error.config.ts";
import kernelConfig from "../kernel.config.ts";
import routerConfig from "../router.config.ts";
import validatorConfig from "../validator.config.ts";
 
const app = new Kernel(kernelConfig);
 
app.use(validator(validatorConfig));
app.use(router(routerConfig));
 
app.catch(errorHandler(errorConfig));
 
export default {
  fetch: (request: Request) => {
    return app.respond(request);
  },
};

Each package is instantiated with its own configuration, keeping concerns separated and the bootstrap file easy to scan at a glance.

If you'd prefer to consolidate further, you can create a single wrapper config file that composes all your individual configs together:

import error from "./config/error.ts";
import router from "./config/router.ts";
import kernel from "./config/kernel.ts";
import validator from "./config/validator.ts";
 
const config = {
  error,
  router,
  kernel,
  validator,
};
 
export default config;

Your bootstrap file can then reference this single config object, making it even more concise:

import { Kernel } from "@raptor/kernel";
 
import router from "@raptor/router";
import validator from "@raptor/validator";
import errorHandler from "@raptor/error";
 
// Load wrapper configuration file.
import config from "../raptor.config.ts";
 
const app = new Kernel(config.kernel);
 
app.use(validator(config.validator));
app.use(router(config.router));
 
app.catch(errorHandler(config.error));

The individual config files remain unchanged - this is purely an organisational convenience rather than a new concept. Both approaches are valid; it comes down to personal preference.

Configuration Files

Each package has its own typed Config interface which you can import and use with the satisfies keyword. This gives you type checking without losing the literal types of your config values.

Router example

import type { Config } from "@raptor/router";
 
import { userRoutes } from "./routes/user.ts";
import { authRoutes } from "./routes/auth.ts";
 
export default {
  /**
   * Whether the router should throw a 404 Not Found error when no route
   * matches the incoming request. When false, the request is passed to
   * the next middleware instead.
   */
  throwNotFound: true,
 
  /**
   * The routes and route groups available to the application.
   */
  routes: [
    ...userRoutes,
    ...authRoutes,
  ],
} satisfies Config;

Assembling Configs

Since config files are plain TypeScript, you can build them up however makes sense for your project. Rules, routes, and other values can be defined elsewhere and composed together in the config file:

Validator example

import type { Config } from "@raptor/validator";
 
import { userRules } from "./rules/user.ts";
import { productRules } from "./rules/product.ts";
 
export default {
  rules: {
    ...userRules,
    ...productRules,
  },
} satisfies Config;

Overriding Defaults

Some packages ship with built-in defaults that can be overridden through the config. For example, the validator comes with a set of built-in rules. You can replace any of them by passing a rule with the same name:

import type { Config } from "@raptor/validator";
 
import { myCustomEmail } from "./rules/email.ts";
 
export default {
  rules: {
    // Replaces the built-in email rule with your own implementation.
    email: myCustomEmail,
  },
} satisfies Config;

Using the Class Directly

The helper functions are the recommended approach for most use cases, but if you need more control — such as registering rules dynamically at runtime - you can use the underlying class directly:

import { Validator } from "@raptor/validator";
 
const instance = new Validator(validatorConfig);
 
instance.getParser().register("custom", customRule);
 
app.use(instance.handle);

This gives you the full API surface of each package without giving up the config-driven approach for the common case.

Next steps

Now that you understand how easy it is to configure your Raptor project, it's time to dive right in with Middleware.

© 2026 Raptor