# Nile Nile is a TypeScript-first, service and actions oriented backend framework for building modern, fast, safe, and AI-ready backends. Instead of defining routes, controllers, and middleware chains, you define actions (plain functions that take data and return results), group them into services, and get a predictable API with validation, error handling, and JSON Schema export automatically. The framework uses a single POST endpoint for all operations with three intents: `explore` (discover services), `execute` (call actions), and `schema` (get validation schemas). The framework provides structured logging, CORS control, JWT authentication, file uploads, database utilities with Drizzle ORM integration, and a Result pattern (Ok/Err) for predictable error handling. Every action with a Zod validation schema automatically exports its parameters as JSON Schema, enabling AI agents to discover and call your API without custom integration code. ## createNileServer Creates and bootstraps a Nile server instance, wiring together the Action Engine, shared context, and interface layers. This is the primary entry point for a Nile application. ```typescript import { createNileServer, createLogger } from "@nilejs/nile"; import { services } from "./services/services.config"; import { db } from "./db/client"; const logger = createLogger("my-app", { chunking: "monthly" }); const server = createNileServer({ serverName: "my-app", services, resources: { logger, database: db }, rest: { baseUrl: "/api", host: "localhost", port: 8000, allowedOrigins: ["http://localhost:3000"], enableStatus: true, }, auth: { secret: process.env.JWT_SECRET!, method: "header", // or "cookie" }, onBeforeActionHandler: ({ nileContext, action, payload }) => { // Global hook: runs before every action console.log(`Executing ${action.name}`); return Ok(payload); }, onAfterActionHandler: ({ nileContext, action, payload, result }) => { // Global hook: runs after every action return result; }, onBoot: { fn: async (context) => { // Run on server boot (e.g., push database schema) console.log("Server booted!"); }, }, }); if (server.rest) { const { fetch } = server.rest.app; Bun.serve({ port: 8000, fetch }); console.log("Server running at http://localhost:8000"); } ``` ## createAction Creates a typed action definition with validation, handler, and optional hooks. Actions are the core building blocks that contain your business logic. ```typescript import { type Action, createAction } from "@nilejs/nile"; import { Ok, Err } from "slang-ts"; import z from "zod"; const createTaskSchema = z.object({ title: z.string().min(1, "Title is required"), description: z.string().optional().default(""), status: z.enum(["pending", "in-progress", "done"]).optional().default("pending"), }); const createTaskHandler = async (data: Record) => { const task = { id: crypto.randomUUID(), title: data.title as string, description: data.description as string, status: data.status as string, created_at: new Date().toISOString(), }; // Return Ok for success, Err for failure if (!task.title) { return Err("Title cannot be empty"); } return Ok({ task }); }; export const createTaskAction: Action = createAction({ name: "create", description: "Create a new task", handler: createTaskHandler, validation: createTaskSchema, isProtected: false, // Set to true to require JWT auth hooks: { before: [ { service: "audit", action: "logAccess", isCritical: false } ], after: [ { service: "notifications", action: "notify", isCritical: false } ] }, }); ``` ## createService / createServices Groups actions into services with metadata. Services organize related actions and provide discoverability through the explore intent. ```typescript import { createService, createServices, type Services } from "@nilejs/nile"; import { createTaskAction } from "./tasks/create"; import { listTaskAction } from "./tasks/list"; import { getTaskAction } from "./tasks/get"; import { updateTaskAction } from "./tasks/update"; import { deleteTaskAction } from "./tasks/delete"; // Single service const tasksService = createService({ name: "tasks", description: "Task management with CRUD operations", meta: { version: "1.0.0" }, actions: [ createTaskAction, listTaskAction, getTaskAction, updateTaskAction, deleteTaskAction, ], }); // Multiple services export const services: Services = createServices([ tasksService, { name: "users", description: "User management", actions: [/* user actions */], }, ]); ``` ## REST API - Execute Intent Execute a specific service action by sending a POST request with the execute intent. The action's Zod schema validates the payload before the handler runs. ```bash # Create a task curl -X POST http://localhost:8000/api/services \ -H "Content-Type: application/json" \ -d '{ "intent": "execute", "service": "tasks", "action": "create", "payload": { "title": "Ship it", "status": "pending" } }' # Response (success): # { # "status": true, # "message": "Action 'tasks.create' executed", # "data": { # "task": { # "id": "a1b2c3d4-...", # "title": "Ship it", # "status": "pending" # } # } # } # Response (validation error): # { # "status": false, # "message": "Validation failed: title - Required", # "data": {} # } ``` ## REST API - Explore Intent Discover available services and actions using the explore intent. Use wildcards (`*`) to list all services or all actions within a service. ```bash # List all services curl -X POST http://localhost:8000/api/services \ -H "Content-Type: application/json" \ -d '{ "intent": "explore", "service": "*", "action": "*", "payload": {} }' # Response: # { # "status": true, # "message": "Available services", # "data": { # "result": [ # { # "name": "tasks", # "description": "Task management with CRUD operations", # "actions": ["create", "list", "get", "update", "delete"] # } # ] # } # } # List actions in a specific service curl -X POST http://localhost:8000/api/services \ -H "Content-Type: application/json" \ -d '{ "intent": "explore", "service": "tasks", "action": "*", "payload": {} }' # Response: # { # "status": true, # "message": "Actions for 'tasks'", # "data": { # "result": [ # { "name": "create", "description": "Create a new task", "isProtected": false, "validation": true }, # { "name": "list", "description": "List all tasks", "isProtected": false, "validation": false } # ] # } # } ``` ## REST API - Schema Intent Fetch JSON Schema for action validation. This enables AI agents to discover action parameters for tool calling. ```bash # Get schema for a specific action curl -X POST http://localhost:8000/api/services \ -H "Content-Type: application/json" \ -d '{ "intent": "schema", "service": "tasks", "action": "create", "payload": {} }' # Response: # { # "status": true, # "message": "Schema for 'tasks.create'", # "data": { # "create": { # "type": "object", # "properties": { # "title": { "type": "string", "minLength": 1 }, # "description": { "type": "string", "default": "" }, # "status": { "type": "string", "enum": ["pending", "in-progress", "done"], "default": "pending" } # }, # "required": ["title"] # } # } # } # Get all schemas (for AI agents) curl -X POST http://localhost:8000/api/services \ -H "Content-Type: application/json" \ -d '{ "intent": "schema", "service": "*", "action": "*", "payload": {} }' ``` ## createModel Creates a CRUD model for a Drizzle table with auto-validation, error handling, and pagination. Eliminates repetitive boilerplate for database operations. ```typescript import { createModel } from "@nilejs/nile"; import { pgTable, text, uuid, timestamp } from "drizzle-orm/pg-core"; import { db } from "./client"; // Define Drizzle schema export const tasks = pgTable("tasks", { id: uuid("id").primaryKey().defaultRandom(), title: text("title").notNull(), description: text("description"), status: text("status").notNull().default("pending"), created_at: timestamp("created_at").notNull().defaultNow(), updated_at: timestamp("updated_at").notNull().defaultNow(), }); // Create model with explicit db export const taskModel = createModel(tasks, { db, name: "task" }); // Or use context-resolved db (resolved at call time from getContext()) // export const taskModel = createModel(tasks, { name: "task" }); // Usage in action handlers: // Create const createResult = await taskModel.create({ data: { title: "New task", status: "pending" } }); if (createResult.isOk) { console.log("Created:", createResult.value); } // Find by ID const findResult = await taskModel.findById("uuid-123"); // Update const updateResult = await taskModel.update({ id: "uuid-123", data: { status: "done" } }); // Delete const deleteResult = await taskModel.delete("uuid-123"); // Find all (ordered by created_at desc) const allResult = await taskModel.findAll(); // Offset pagination const pageResult = await taskModel.findPaginated({ limit: 20, offset: 0 }); // Returns: { items: Task[], total: number, hasMore: boolean } // Cursor pagination const cursorResult = await taskModel.findPaginated({ limit: 20, cursor: "last-item-id", cursorColumn: "id" }); // Returns: { items: Task[], nextCursor: string | null, hasMore: boolean } // Transaction variant for use within transactions const txResult = await taskModel.createTx({ data: { title: "In transaction" }, dbx: tx // transaction instance }); ``` ## createNileClient (Frontend) Creates a type-safe client for interacting with Nile backend services from frontend applications. ```typescript import { createNileClient } from "@nilejs/client"; import type { ServicePayloads } from "./generated/types"; // Generated by CLI // Basic client const nile = createNileClient({ baseUrl: "http://localhost:8000/api", credentials: "include", timeout: 30000, headers: { "X-Custom-Header": "value" }, }); // Type-safe client with generated types const typedNile = createNileClient({ baseUrl: "/api" }); // Invoke an action const { error, data } = await nile.invoke({ service: "tasks", action: "create", payload: { title: "Buy milk", status: "pending" }, }); if (error) { console.error("Action failed:", error); } else { console.log("Created task:", data); } // Explore services const { data: services } = await nile.explore({ service: "*", action: "*" }); // Get action schemas const { data: schemas } = await nile.schema({ service: "tasks", action: "*" }); // Upload files const { error: uploadError, data: uploadData } = await nile.upload({ service: "documents", action: "upload", files: { document: fileInput.files[0] }, fields: { title: "My Document" }, }); ``` ## createLogger Creates a structured logger with time-based file chunking. Logs are persisted as NDJSON for easy parsing and filtering. ```typescript import { createLogger, createLog, getLogs, type LogFilter } from "@nilejs/nile"; // Create logger bound to app name with monthly chunking const logger = createLogger("my-app", { chunking: "monthly" }); // Chunking options: "monthly", "daily", "weekly", "none" // Log messages at different levels logger.info({ atFunction: "createTask", message: "Task created successfully", data: { taskId: "123", title: "New task" } }); logger.warn({ atFunction: "updateTask", message: "Task not found, skipping update", data: { taskId: "456" } }); logger.error({ atFunction: "deleteTask", message: "Failed to delete task", data: { taskId: "789", error: "Database connection failed" } }); // Direct log creation (lower-level) const logId = createLog({ appName: "my-app", atFunction: "bootstrap", message: "Application started", level: "info", data: { port: 8000 } }, { chunking: "monthly" }); // Retrieve logs with filters const filters: LogFilter = { appName: "my-app", level: "error", from: new Date("2024-01-01"), to: new Date("2024-12-31"), }; const errorLogs = getLogs(filters, { chunking: "monthly" }); console.log(`Found ${errorLogs.length} error logs`); ``` ## getContext Retrieves the runtime NileContext for accessing shared resources, database, and context storage from anywhere in your application. ```typescript import { getContext, type NileContext } from "@nilejs/nile"; // In action handlers, get typed context const handler = async (data: Record) => { const ctx = getContext(); // Access database from context const db = ctx.resources?.database; // Access logger const logger = ctx.resources?.logger; // Use context key-value store ctx.set("requestId", crypto.randomUUID()); const requestId = ctx.get("requestId"); // Access auth result (when using JWT auth) const authResult = ctx.getAuth(); if (authResult) { console.log("User:", authResult.userId); console.log("Org:", authResult.organizationId); } // Get user helper (combines auth fields) const user = ctx.getUser(); // Returns: { userId, organizationId, ...claims } | undefined // Session management ctx.setSession("user", { preferences: { theme: "dark" } }); const userSession = ctx.getSession("user"); return Ok({ requestId }); }; ``` ## handleFormDataRequest / validateFiles Handle multipart form-data uploads with validation for file size, count, and allowed types. ```typescript import { createAction, handleFormDataRequest, validateFiles, type UploadsConfig, type Action } from "@nilejs/nile"; import { Ok, Err } from "slang-ts"; const uploadConfig: UploadsConfig = { limits: { maxFileSize: 10 * 1024 * 1024, // 10MB per file maxTotalSize: 50 * 1024 * 1024, // 50MB total maxFiles: 5, minFileSize: 1, // Reject empty files maxFilenameLength: 255, }, allowlist: { mimeTypes: ["image/jpeg", "image/png", "application/pdf"], extensions: [".jpg", ".jpeg", ".png", ".pdf"], }, }; // Action configured for multipart uploads export const uploadAction: Action = createAction({ name: "upload", description: "Upload documents", isSpecial: { contentType: "multipart/form-data", uploadMode: "structured", // or "flat" }, handler: async (data: Record, context) => { // Files are in data.files, fields in data.fields const files = data.files as Record; const fields = data.fields as Record; const document = files.document as File; const title = fields.title || document.name; // Process the uploaded file const buffer = await document.arrayBuffer(); return Ok({ filename: document.name, size: document.size, mimeType: document.type, title, }); }, }); // Manual file validation (if needed outside request handling) const files = [file1, file2]; const validationResult = validateFiles(files, uploadConfig); if (!validationResult.status) { console.error("Validation failed:", validationResult.message); // validationResult.data contains error details } ``` ## CLI Commands The `@nilejs/cli` package provides scaffolding and code generation commands for Nile projects. ```bash # Scaffold a new project npx @nilejs/cli new my-app cd my-app && bun install && bun run dev # Generate a new service with demo action nile gen service users # Creates: src/services/users/sample.ts, src/services/users/index.ts # Generate a new action in existing service nile gen action users get-user # Creates: src/services/users/get-user.ts # Extract Zod schemas and generate TypeScript types nile gen schema # Creates: src/generated/schemas.ts, src/generated/types.ts # With custom paths nile gen schema --entry src/services/config.ts --output src/types # Generated types example (src/generated/types.ts): # export type TasksCreatePayload = z.infer; # export type TasksUpdatePayload = z.infer; # export interface ServicePayloads { # tasks: { # create: TasksCreatePayload; # update: TasksUpdatePayload; # // ... # }; # } ``` ## JWT Authentication Configure JWT authentication for protected actions. Supports both header and cookie-based token extraction. ```typescript import { createNileServer, verifyJWT, type AuthConfig } from "@nilejs/nile"; // Server-side configuration const server = createNileServer({ serverName: "my-app", services, auth: { secret: process.env.JWT_SECRET!, method: "header", // Extract from Authorization header headerName: "authorization", // default // Or use cookies: // method: "cookie", // cookieName: "auth_token", }, }); // Mark actions as protected const protectedAction: Action = createAction({ name: "sensitive-operation", description: "Requires authentication", isProtected: true, // Requires valid JWT handler: async (data, context) => { const ctx = getContext(); const auth = ctx.getAuth(); // auth contains: userId, organizationId, claims console.log("Authenticated user:", auth?.userId); return Ok({ result: "success" }); }, }); // Client-side: include auth header const nile = createNileClient({ baseUrl: "/api", headers: { Authorization: `Bearer ${token}`, }, }); // Or with credentials for cookies const nileWithCookies = createNileClient({ baseUrl: "/api", credentials: "include", }); ``` Nile is designed for developers who want to focus on business logic without the ceremony of traditional backend frameworks. The service/action model naturally maps to how AI agents discover and call APIs, making it ideal for building AI-ready backends. The single-endpoint design with intents (explore, execute, schema) provides a consistent interface whether you're building REST APIs, integrating with LLMs, or creating internal tools. For complex applications, Nile integrates seamlessly with Drizzle ORM for database operations, provides structured logging with time-based chunking, and supports file uploads with comprehensive validation. The Result pattern (Ok/Err) ensures predictable error handling throughout your codebase, eliminating try-catch spaghetti and mysterious 500 errors.