Lesan is a blazing-fast, cross-platform web framework and Object Document Mapper (ODM) designed to give you the exact data-fetching flexibility of GraphQL, without its heavy processing overhead, while maintaining the simplicity of REST.
Even though NoSQL is very fast, its complexities are very troublesome for large-scale projects. On the other hand, GraphQL shines in client-server connectivity but it has several weaknesses and is somewhat complex, adding another layer of complexity to the project. That’s why we created LESAN.
🚀 Now Cross-Platform! Lesan natively supports Node.js, Bun, and Deno with zero configuration. Write your code once and run it anywhere!
- 🎯 Client-Driven Projections: Similar to GraphQL, clients dictate exactly the shape and depth of the data they receive, including nested relations, but without the CPU cost of parsing a complex query language.
- 🤝 One-Directional Magic, Bi-Directional Power: Relationships in Lesan are an absolute game-changer. You define a relationship from one direction (the model that needs it most), and Lesan automatically handles the immensely complex bi-directional embedding, syncing, array limits, sorting, and cascading updates/deletes behind the scenes. Goodbye to complex JOINs and manual NoSQL array updates!
- 🛡️ End-to-End Type Safety: Run your server with the
typeGeneration: trueflag, and Lesan will auto-generate perfect client-side TypeScript definitions and a custom fetch wrapper. This guarantees flawless frontend integration and autocomplete. - ⚡ Insane Performance: Stripped of unnecessary middleware bloat and built directly on the official MongoDB driver, Lesan's direct-to-database routing crushes traditional REST/ORM and GraphQL server speeds.
- 🧩 Built for Microservices: The action-based architecture inherently supports breaking large monolithic systems down into isolated microservices effortlessly.
This video is an introductory tutorial on the Lesan framework in the Farsi language.
We use this formula to calculate the difference : (B - A) ÷ A * 100
As you see on the chart:
- Lesan return data to client
1168%faster than theprisma-express-rest. Which usespostgresas a database. - Lesan return data to client
1417%faster than theprisma-express-graphql. Which usespostgresas a database. - Lesan return data to client
4435%faster than themongoose-express-rest(Note that we did not sort in this query) - Lesan return data to client
72289%faster than themongo-express-rest(Note that we did not sort in this query) - Lesan return data to client
298971%faster than themongoose-express-rest(used sortby)
Maybe we created the most performant framework in the world! see more detailed benchmark
Note: With our new cross-platform architecture, Lesan achieves over 10,000 requests per second on Bun and Deno! Check out our Cross-Platform Benchmarks.
- Introduction
- Installation
- Getting start
- Advanced Guide
- Lesan Philosophy
- Structures
- API Reference
- Types
- Cross-Platform Migration Guide
- Examples (Node.js, Bun, Deno)
Lesan is designed to work seamlessly across all major JavaScript runtimes.
npm install @hemedani/lesan mongodbbun add @hemedani/lesan mongodbimport { lesan } from "jsr:@hemedani/lesan";
import { MongoClient } from "npm:mongodb";Create a file called main.ts (or main.js) and paste the code below into it:
import {
ActFn,
Document,
Filter,
lesan,
number,
object,
ObjectId,
optional,
size,
string,
} from "@hemedani/lesan"; // Use "jsr:@hemedani/lesan" for Deno
import { MongoClient } from "mongodb"; // Use "npm:mongodb" for Deno
const coreApp = lesan();
const client = await new MongoClient("mongodb://127.0.0.1:27017/").connect();
const db = client.db("civil");
coreApp.odm.setDb(db);
// ================== MODEL SECTION ==================
// ------------------ Country Model ------------------
const countryPure = {
name: string(),
population: number(),
abb: string(),
};
const countryRelations = {};
const countries = coreApp.odm.newModel("country", countryPure, countryRelations);
// ------------------ User Model ------------------
const userPure = {
name: string(),
age: number(),
};
const users = coreApp.odm.newModel("user", userPure, {
country: {
optional: false,
schemaName: "country",
type: "single",
relatedRelations: {
users: {
type: "multiple",
limit: 50,
sort: {
field: "_id",
order: "desc",
},
},
},
},
});
// ================== FUNCTIONS SECTION ==================
// ------------------ Add Country ------------------
const addCountryValidator = () => {
return object({
set: object(countryPure),
get: coreApp.schemas.selectStruct("country", { users: 1 }),
});
};
const addCountry: ActFn = async (body) => {
const { name, population, abb } = body.details.set;
return await countries.insertOne({
doc: {
name,
population,
abb,
},
projection: body.details.get,
});
};
coreApp.acts.setAct({
schema: "country",
actName: "addCountry",
validator: addCountryValidator(),
fn: addCountry,
});
// ------------------ Get Countries ------------------
const getCountriesValidator = () => {
return object({
set: object({
page: number(),
limit: number(),
}),
get: coreApp.schemas.selectStruct("country", 1),
});
};
const getCountries: ActFn = async (body) => {
let {
set: { page, limit },
get,
} = body.details;
page = page || 1;
limit = limit || 50;
const skip = limit * (page - 1);
return await countries.find({ projection: get, filters: {} }).skip(skip).limit(limit).toArray();
};
coreApp.acts.setAct({
schema: "country",
actName: "getCountries",
validator: getCountriesValidator(),
fn: getCountries,
});
// --------------------- Add User ----------------------
const addUserValidator = () => {
return object({
set: object({
...userPure,
country: string(),
}),
get: coreApp.schemas.selectStruct("user", 1),
});
};
const addUser: ActFn = async (body) => {
const { country, name, age } = body.details.set;
return await users.insertOne({
doc: { name, age },
projection: body.details.get,
relations: {
country: {
_ids: new ObjectId(country),
relatedRelations: {
users: true,
},
},
},
});
};
coreApp.acts.setAct({
schema: "user",
actName: "addUser",
validator: addUserValidator(),
fn: addUser,
});
// --------------------- Get Users ----------------------
const getUsersValidator = () => {
return object({
set: object({
page: number(),
take: number(),
countryId: optional(size(string(), 24)),
}),
get: coreApp.schemas.selectStruct("user", { country: 1 }),
});
};
const getUsers: ActFn = async (body) => {
let {
set: { page, limit, countryId },
get,
} = body.details;
page = page || 1;
limit = limit || 50;
const skip = limit * (page - 1);
const filters: Filter<Document> = {};
countryId && (filters["country._id"] = new ObjectId(countryId));
return await users.find({ projection: get, filters }).skip(skip).limit(limit).toArray();
};
coreApp.acts.setAct({
schema: "user",
actName: "getUsers",
validator: getUsersValidator(),
fn: getUsers,
});
// ================== RUN SECTION ==================
coreApp.runServer({ port: 1366, typeGeneration: false, playground: true });Depending on your runtime, run the following command in your terminal:
Node.js (requires tsx or similar for TypeScript):
npx tsx main.tsBun:
bun run main.tsDeno:
deno run -A main.tsYou should see this message:
HTTP webserver running.
please send a post request to http://localhost:1366/lesan
you can visit playground on http://localhost:1366/playground
Listening on http://localhost:1366/Now you can visit the playground at http://localhost:1366/playground and send requests to the server for addCountry, addUser, and getUsers.

Alternatively, you can send a POST request to http://localhost:1366/lesan with postman including the following in JSON format inside the body in order to retrieve the desired data:
{
"service": "main",
"model": "country",
"act": "addCountry",
"details": {
"set": {
"name": "Iran",
"population": 85000000,
"abb": "IR"
},
"get": {
"_id": 1,
"name": 1,
"population": 1,
"abb": 1
}
}
}In Lesan, relationships are One-Directional in definition, but fully embedded bi-directionally.
When you link a User to a Country, Lesan seamlessly embeds the country details inside the user, and automatically embeds the user details inside the country's users array (even keeping the top 50 users sorted automatically!).
We handle all the heavy lifting of keeping this embedded data perfectly in sync across your entire database for blazing-fast reads. You can also control the exact level of penetration into the relationship depth. On the client-side, you simply describe what you want and get back exactly what you described—with zero backend refactoring!
Mehrshad Cheshm Khavari |
Mahdi Ramezani |
Alireza Safaierad |
ali davodi |
sorena ganji |
![]() Node Master |
Alireza Safaierad |
Erfan Asadi |


