Skip to content

Serverless Deployment

Karin is architected with a "Serverless-First" mindset. The framework core decouples the Application Logic (Controllers/Services) from the underlying Runtime (HTTP Server).

This decoupling allows Karin to run seamlessly on Edge networks like Cloudflare Workers, Vercel Edge, and Deno Deploy, where traditional long-running Node.js processes are replaced by ephemeral function invocations.

Concept: The Pop-Up Store

A traditional server is a Flagship Store: always open, always staffed, paying rent 24/7. Serverless is a Pop-Up Store: It appears instantly when a customer (request) arrives, serves them, and vanishes. It is extremely cost-effective but requires a different logistics chain (Statelessness).


The Boot Pattern: Inversion of Control

In a standard application, you call app.listen(3000). The application binds to a port and waits. In Serverless, you do not listen. The platform listens for you and invokes your handler. You must export a handler function.

We use KarinFactory.serverless() instead of KarinFactory.create().

typescript
// main.ts
import { KarinFactory } from "@project-karin/core";
import { HonoAdapter } from "@project-karin/platform-hono";
import { UserController } from "./user/user.controller";

// 1. Initialize the Adapter manually (to configure CORS, etc.)
const adapter = new HonoAdapter();
adapter.enableCors();

// 2. Create the Handler (Do not await!)
export default KarinFactory.serverless(adapter, {
  // CRITICAL: Disable file scanning. Bundlers like esbuild cannot require() dynamic paths.
  scan: false,
  
  // You MUST explicitly register all components
  controllers: [UserController],
  providers: [JwtStrategy, UserService], 
  plugins: [config, db, auth],
});

The Bundler Constraint (No Dynamic Imports)

Serverless environments use aggressive bundlers (esbuild, webpack, vite) to compile your code into a single file. These bundlers analyze your import graph at build time.

Therefore, Karin's scan: true feature (which reads the filesystem at runtime to find .controller.ts files) WILL NOT WORK.

IMPORTANT

You must set { scan: false } and manually import and register every Controller and Provider in main.ts.

The Metadata Constraint (esbuild & Decorators)

TypeScript's emitDecoratorMetadata feature relies on the compiler emitting runtime type information.

  • TSC (Standard): Emits full metadata (design:paramtypes).
  • esbuild (Cloudflare/Vite): Does NOT emit deep metadata by default.

This breaks Dependency Injection if you rely on implicit typing.

Broken in Cloudflare:

typescript
@Post()
create(@Body() dto: CreateUserDto) { 
  // Error: Karin cannot know that 'dto' is of type 'CreateUserDto' 
}

Fixed (Explicit Typing):

typescript
@Post()
create(@Body(CreateUserDto) dto: CreateUserDto) {
  // Success: We explicitly passed the Class Constructor to the decorator
}

Warm Starts & Connection Reuse

Even though serverless functions are ephemeral, the provider often reuses the same V8 Isolate for multiple requests (Warm Start).

Karin's architecture takes advantage of this.

  1. Global Scope: The app instance is created in the global scope (outside the fetch handler).
  2. Singleton Re-hydration: On the first request (Cold Start), Karin boots calls onPluginInit.
  3. Subsequent Requests: Karin skips the boot phase and routes the request immediately.

This is why MongoosePlugin and DrizzlePlugin have special logic to reuse existing connections instead of opening new ones every 100ms.

platform-specific Examples

Cloudflare Workers (wrangler.toml)

Cloudflare uses the standard Web API (Request / Response).

toml
name = "my-karin-api"
main = "src/main.ts"
compatibility_date = "2024-04-05"

[vars]
LOG_LEVEL = "debug"

Vercel / Next.js API Routes

You can wrap the handler for Vercel's proprietary signatures.

typescript
// api/[[...route]].ts
import handler from "../src/main"; // Your Karin export

export const GET = handler.fetch;
export const POST = handler.fetch;
// ... export other methods

Released under the MIT License.