Skip to main content

Server API

The Server API is responsible for handling HTTP requests, serving static files, providing the interactive playground, and routing client requests to the appropriate action handlers.

lesan() โ€” Main Entry Pointโ€‹

The lesan() function is the primary entry point for the framework. It initializes all core modules and returns an object containing everything you need to build your application.

import { lesan } from "@hemedani/lesan";

const coreApp = lesan();

Return Valueโ€‹

PropertyTypeDescription
schemasSchemasAPISchema definition and introspection functions
actsActsAPIAction registration and discovery functions
odmODMAPIDatabase connection and model creation
runServerServerRunnerHTTP server initialization function
contextFnsContextFnsRequest-scoped context management
generateSchemTypes() => Promise<void>TypeScript type generation
const coreApp = lesan();

// Access individual modules
coreApp.schemas; // Schema management
coreApp.acts; // Action registration
coreApp.odm; // Database operations
coreApp.runServer; // Server startup
coreApp.contextFns; // Context management

runServer() โ€” Starting the Serverโ€‹

The runServer function starts an HTTP server that listens for incoming requests.

await coreApp.runServer({
port: 8000,
playground: true,
typeGeneration: true,
staticPath: ["./public"],
cors: "*",
});

Optionsโ€‹

OptionTypeDefaultDescription
portnumber8000Port number to listen on
playgroundbooleanfalseEnable the interactive API playground
typeGenerationbooleanfalseAuto-generate TypeScript declaration files
staticPathstring[][]Array of directories to serve static files from
cors"*" | string[]undefinedCORS configuration

Server Outputโ€‹

When the server starts, you'll see:

๐Ÿš€ Lesan server is running successfully!
๐Ÿ“ก API Endpoint: http://localhost:8000/lesan
๐ŸŽฎ Playground: http://localhost:8000/playground

CORS Configurationโ€‹

Allow all originsโ€‹

await coreApp.runServer({
port: 8000,
cors: "*",
});

Allow specific originsโ€‹

await coreApp.runServer({
port: 8000,
cors: ["https://example.com", "https://app.example.com"],
});

Request Handlingโ€‹

Lesan uses a single endpoint pattern. All API requests are sent to POST /lesan with a JSON body describing the desired action.

Request Body Structure (TLesanBody)โ€‹

interface TLesanBody {
service?: string; // Service name (default: "main")
model: string; // Schema/model name
act: string; // Action name
details: {
set: Record<string, any>; // Input data
get: Record<string, any>; // Projection / desired output shape
};
}

Example Requestโ€‹

curl -X POST http://localhost:8000/lesan \
-H "Content-Type: application/json" \
-d '{
"service": "main",
"model": "country",
"act": "addCountry",
"details": {
"set": { "name": "Iran", "population": 85000000 },
"get": { "name": 1, "population": 1 }
}
}'

Response Formatโ€‹

{
"body": { /* action result */ },
"success": true
}

On error:

{
"body": {
"message": "Error description"
},
"success": false
}

Context System (contextFns)โ€‹

The context system carries request-scoped values through your action functions. This is useful for authentication, request metadata, and passing data between middleware and handlers.

Context Shape (LesanContext)โ€‹

interface LesanContext {
Headers: Headers; // Request headers
body: TLesanBody | null; // Parsed request body
[key: string]: any; // Any custom values you add
}

Context Functionsโ€‹

FunctionDescription
getContextModel()Get the current context object
setContext(obj)Replace the entire context with a new object
addContexts(con)Set context to a specific value
addContext(con)Merge values into the existing context
addReqToContext(req)Add the raw Request object to context
addHeaderToContext(headers)Add headers to context
addBodyToContext(body)Add the parsed body to context

Using Context in Actionsโ€‹

import { contextFns } from "@hemedani/lesan";

const myAction = async (body) => {
// Access the current user from context
const context = contextFns.getContextModel();
const currentUser = context.user;
const headers = context.Headers;

// Your business logic here
return { success: true };
};

Pre-hooks and Contextโ€‹

You can use preAct and preValidation hooks to populate the context before your action runs:

coreApp.acts.setAct({
schema: "user",
actName: "getProfile",
validator: myValidator,
fn: getProfileFn,
preValidation: [
async () => {
// Validate JWT token and add user to context
const token = contextFns.getContextModel().Headers.get("authorization");
const user = await verifyToken(token);
contextFns.addContext({ user });
}
],
});
tip

Go deeper

For the exact lifecycle order (preValidation โ†’ validation โ†’ preAct โ†’ fn), how validationRunType works, and writing reusable hooks, see Request Lifecycle & Hooks.


Acts System (acts)โ€‹

The Acts system is where you define your API endpoints. Each action maps to a specific operation on a schema.

setAct() โ€” Register an Actionโ€‹

coreApp.acts.setAct({
schema: "country",
actName: "addCountry",
validator: addCountryValidator,
fn: addCountryFn,
preAct: [loggingHook],
preValidation: [authHook],
validationRunType: "assert",
});

Act Configuration (ActInp)โ€‹

PropertyTypeRequiredDescription
schemastringโœ…Schema name this action belongs to
actNamestringโœ…Unique name for this action
validatorStruct<any>โœ…Superstruct validator for input validation
fnActFnโœ…The action handler function
preActFunction[]โŒHooks to run before the action function
preValidationFunction[]โŒHooks to run before validation
validationRunType"assert" | "create"โŒValidation mode (default: "assert")

Action Function (ActFn)โ€‹

type ActFn = (body: TLesanBody) => any;

The action function receives the full request body and should return the response data.

const addCountryFn = async (body) => {
const { name, population } = body.details.set;
const { get } = body.details;

return await country.insertOne({
doc: { name, population },
projection: get,
});
};

Act Introspectionโ€‹

Lesan provides functions to inspect registered actions at runtime:

// Get all service names
coreApp.acts.getServiceKeys(); // ["main", "ecommerce", "blog"]

// Get all acts for a schema
coreApp.acts.getActs("country"); // { addCountry: Act, getCountries: Act }

// Get all act names for a schema in a service
coreApp.acts.getActsKeys("main", "country"); // ["addCountry", "getCountries"]

// Get a specific act
coreApp.acts.getAct("main", "country", "addCountry");

// Get all acts across all services
coreApp.acts.getAtcsWithServices();

// Get all main acts
coreApp.acts.getMainActs();

Servicesโ€‹

Lesan supports microservice-style architecture through the services system. You can register local acts or proxy requests to other services.

tip

Go deeper

For the full microservices walkthrough โ€” how URL forwarding rewrites service: "main", in-process vs remote services, and a real two-app layout โ€” see Microservices.

Local Service (Default)โ€‹

All acts registered with setAct are part of the main service by default.

Proxy Service (URL-based)โ€‹

You can forward requests to another Lesan instance:

// Register a remote service
coreApp.acts.setService("ecommerce", "https://api.ecommerce.com/lesan");

// Now requests with service: "ecommerce" will be forwarded

Local Service with Actsโ€‹

// Register a local service with its own acts
const ecommerceActs = {
product: {
getProduct: { validator: productValidator, fn: getProductFn },
},
};

coreApp.acts.setService("ecommerce", ecommerceActs);

Service Introspectionโ€‹

// Get a service
coreApp.acts.getService("main"); // Returns Acts object
coreApp.acts.getService("ecommerce"); // Returns string URL or Acts

Validation Modesโ€‹

Lesan supports two validation modes via Superstruct:

assert (Default)โ€‹

Throws an error if validation fails. Use this for strict input validation.

coreApp.acts.setAct({
schema: "user",
actName: "createUser",
validator: object({
set: object({ name: string(), email: string() }),
get: object(),
}),
fn: createUserFn,
validationRunType: "assert", // default
});

createโ€‹

Coerces and fills in default values instead of throwing. Useful for optional fields with defaults.

coreApp.acts.setAct({
schema: "user",
actName: "updateUser",
validator: object({
set: object({
name: defaulted(string(), "Anonymous"),
age: defaulted(number(), 0),
}),
get: object(),
}),
fn: updateUserFn,
validationRunType: "create",
});

Static File Servingโ€‹

Lesan can serve static files alongside your API:

await coreApp.runServer({
port: 8000,
staticPath: ["./public", "./uploads"],
});

Files in these directories will be served at the root path. GET requests that don't match /lesan or /playground will attempt to serve static files.


Playgroundโ€‹

When playground: true is enabled, Lesan serves an interactive web UI at /playground where you can:

  • Browse all schemas and their fields
  • View all registered actions
  • Execute actions with a user-friendly form
  • Inspect request/response payloads
  • Generate TypeScript types
await coreApp.runServer({
port: 8000,
playground: true,
});

Error Handlingโ€‹

Lesan automatically catches errors in your action functions and returns them as JSON responses with appropriate HTTP status codes.

Custom Errorsโ€‹

Throw an HttpError to control the status code:

import { HttpError } from "@hemedani/lesan";

const myAction = async (body) => {
if (!body.details.set.name) {
throw new HttpError("Name is required", 400);
}
// ...
};

Error Responseโ€‹

{
"body": {
"message": "Name is required"
},
"success": false
}

Complete Server Exampleโ€‹

import { lesan, MongoClient, string, number, object, ObjectId } from "@hemedani/lesan";

const coreApp = lesan();

// Connect to MongoDB
const client = await new MongoClient("mongodb://localhost:27017/").connect();
coreApp.odm.setDb(client.db("myapp"));

// Define schema and model
const country = coreApp.odm.newModel("country", {
name: string(),
population: number(),
}, {});

// Define action
const addCountry: ActFn = async (body) => {
return await country.insertOne({
doc: body.details.set,
projection: body.details.get,
});
};

coreApp.acts.setAct({
schema: "country",
actName: "addCountry",
validator: object({
set: object({ name: string(), population: number() }),
get: object(),
}),
fn: addCountry,
});

// Start server
await coreApp.runServer({
port: 8000,
playground: true,
typeGeneration: true,
cors: "*",
});

API Reference Tableโ€‹

Server Functionsโ€‹

FunctionDescription
lesan()Initialize the Lesan framework
runServer(options)Start the HTTP server

Context Functionsโ€‹

FunctionDescription
contextFns.getContextModel()Get current context
contextFns.setContext(obj)Replace context
contextFns.addContext(obj)Merge into context
contextFns.addReqToContext(req)Add raw request
contextFns.addHeaderToContext(headers)Add headers
contextFns.addBodyToContext(body)Add parsed body

Acts Functionsโ€‹

FunctionDescription
acts.setAct(config)Register a new action
acts.getServiceKeys()Get all service names
acts.getActs(schema)Get all acts for a schema
acts.getActsKeys(service, schema)Get act names for a schema
acts.getActKeys(schema)Get act keys for main service
acts.getAct(service, schema, act)Get a specific act
acts.getAtcsWithServices()Get all acts across services
acts.getMainActs()Get all main service acts
acts.getMainAct(schema, actName)Get a specific main act
acts.setService(name, service)Register a service
acts.getService(name)Get a service