Skip to main content

Core Overview

@carno.js/core is the foundation of a Carno application. It provides the HTTP server integration, routing decorators, request context, dependency injection container, middleware pipeline, validation integration, cache service and lifecycle hooks.

The core package is intentionally small. Features that are not required by every application, such as ORM, queues, scheduling, static files, websockets and logging, live in first-party packages that can be added as plugins.

What Core Owns

Core is responsible for:

  • Creating and starting the Bun HTTP server.
  • Registering controllers and compiling route handlers.
  • Resolving constructor dependencies through the DI container.
  • Running middleware around handlers.
  • Parsing request data through Context.
  • Validating DTOs when a validation schema is present.
  • Managing lifecycle hooks.
  • Providing a default CacheService.
  • Importing plugin controllers, services, middleware and routes.

Application Entry Point

Every app starts with a Carno instance.

import { Carno } from '@carno.js/core';
import { UserController } from './user.controller';
import { UserService } from './user.service';

const app = new Carno()
.services([UserService])
.controllers([UserController]);

app.listen(3000);

listen() bootstraps the container, compiles routes and starts Bun's server.

Configuration

The Carno constructor accepts framework-level configuration.

import { Carno, ZodAdapter } from '@carno.js/core';

const app = new Carno({
validation: ZodAdapter,
globalMiddlewares: [],
cors: {
origins: ['https://app.example.com'],
credentials: true,
},
cache: {
prefix: 'my-app',
defaultTtl: 60_000,
},
});

Common options:

OptionDescription
exportsServices exported by a plugin module
globalMiddlewaresMiddleware applied to every route
disableStartupLogDisable the startup console message
corsCORS configuration
validationValidation adapter, true, false or adapter instance
cacheCacheService configuration

Controllers and Routes

Controllers group route handlers behind a base path.

import { Controller, Get, Param } from '@carno.js/core';

@Controller('/users')
export class UserController {
constructor(private users: UserService) {}

@Get('/:id')
findOne(@Param('id') id: string) {
return this.users.findById(id);
}
}

At startup, Carno reads route metadata, resolves the controller from the container and registers handlers with Bun's native route table.

Dependency Injection

Services and controllers are created by the container.

import { Service } from '@carno.js/core';

@Service()
export class UserService {
constructor(private repository: UserRepository) {}
}

The container supports singleton, request and instance scopes, plus useClass and useValue providers. See Dependency Injection for the full model.

Request Context

Each handler receives a Context internally. You can access it with @Ctx() or use parameter decorators such as @Body(), @Param() and @Query().

import { Body, Controller, Ctx, Post, type Context } from '@carno.js/core';

@Controller('/posts')
class PostController {
@Post()
create(@Ctx() ctx: Context, @Body() body: any) {
return ctx.json({ created: true, body }, 201);
}
}

Context lazily parses URL, query, body and locals only when they are accessed.

Middleware Pipeline

Middleware wraps route handlers and is used for cross-cutting concerns.

import { Middleware } from '@carno.js/core';

@Controller('/admin')
@Middleware(AuthMiddleware)
class AdminController {}

Middleware can return early, add data to ctx.locals or transform the final Response.

Validation

Validation is enabled by default with Zod. Add @Schema() to DTO classes and use them with @Body().

import { Body, Post, Schema } from '@carno.js/core';
import { z } from 'zod';

const CreateUserSchema = z.object({
email: z.string().email(),
});

@Schema(CreateUserSchema)
class CreateUserDto {
email!: string;
}

@Post()
create(@Body() body: CreateUserDto) {
return body;
}

Invalid requests return 400 Bad Request before reaching the handler.

Plugins and Modules

A plugin is another Carno instance that contributes routes, services, middleware or framework integration.

export const AuthModule = new Carno({
exports: [AuthService],
});

AuthModule.services([AuthService, PasswordHasher]);
AuthModule.controllers([AuthController]);
const app = new Carno()
.use(AuthModule);

Encapsulation rules:

  • Controllers from a plugin are imported into the main router.
  • Plugin services are private unless listed in exports.
  • Middleware registered by the plugin is merged into the app.
  • Programmatic routes registered by the plugin are copied into the app.

Lifecycle Hooks

Lifecycle hooks let services react to startup and shutdown.

import { OnApplicationInit, Service } from '@carno.js/core';

@Service()
class DatabaseService {
@OnApplicationInit()
async connect() {
// connect before serving traffic
}
}

Use lifecycle hooks for application-level setup and cleanup, not for per-request logic.

  1. Controllers & Routing
  2. Context
  3. Validation
  4. Dependency Injection
  5. Middleware
  6. Lifecycle Events
  7. Caching