- v0.1.13Latest
- v0.1.12
- v0.1.11
- v0.1.10
- v0.1.9
- v0.1.8
- v0.1.7
- v0.1.6
- v0.1.5
- v0.1.4
- v0.1.3
- v0.1.2
- v0.1.1
- v0.1.0
- v0.0.99
- v0.0.98
- v0.0.97
- v0.0.96
- v0.0.95
- v0.0.94
- v0.0.93
- v0.0.92
- v0.0.91
- v0.0.90
- v0.0.89
- v0.0.88
- v0.0.87
- v0.0.86
- v0.0.85
- v0.0.84
- v0.0.83
- v0.0.82
- v0.0.81
- v0.0.80
- v0.0.79
- v0.0.78
- v0.0.77
- v0.0.76
- v0.0.75
- v0.0.74
- v0.0.73
- v0.0.72
- v0.0.71
- v0.0.70
- v0.0.69
- v0.0.68
- v0.0.67
- v0.0.66
- v0.0.65
- v0.0.64
- v0.0.63
- v0.0.62
- 0.0.61
- 0.0.60
- 0.060
- 0.0.59
- 0.0.58
- 0.0.57
- 0.0.56
- 0.0.55
- 0.0.54
- 0.0.53
- 0.0.52
- 0.0.51
documantation is comming …
What is Lesan?
Lesan is a combination of a web server and a MongoDb ODM with some built-in microservice infrastructure.
It also has employed several concepts regarding arbitrary embedding documents and creating SSG contents.
for know is just working on deno, node js version also is comming
Let’s create a simple web server with deno:
You can find complete implementation of this example here.
First of all, create a mod.ts
file and import the latest version of lesan
and assign it to a constant variable called coreApp
:
import { lesan } from "https://deno.land/x/lesan@vx.x.x/mod.ts";
const coreApp = lesan();
Please replace
x.x.x
in the import link with the latest version in releases
Before anything, let’s connect a database
to our app, so add a new MongoDb
instance to your code.
First, import MongoClient
from lesan
:
import { lesan, MongoClient } from "https://deno.land/x/lesan@vx.x.x/mod.ts";
and create a database instance via new MongoClient
:
const client = new MongoClient();
await client.connect("mongodb://localhost:27017/${your_database_name}");
const db = client.database("core");
We should set up the ODM
with a new database instance:
coreApp.odm.setDb(db);
Conceptually, we have three important concepts for designing every model in the database, namely: pure
and inrelation
and outrelation
.
pure
is merely a simple object with key
of string
and a value
similar to SuperStruct structure.
inrelation
represents an array or a single pure
object of another MongoDb collection
, we want to embed in the current document. In SQL
modeling, for every relation we save the key
or id
which we call inrelation
. As an example, we have a blogPost
which has a creator from user
collection and we save pure
model of the user
in blogPost
collection.
outrelation
specifies a relation for a specific collection
but it could contain an unbound set of data that could outgrow the 16MB limit size of a document in MongoDB. Thus we do not even save its key
or id
in SQL
modeling. For example, we have a user
entity who writes many blog posts and we save for example an array
of pure
object of blogPost
in order of the date published for the first pagination in user
collection containing the latest 50 blog posts.
Now let’s get our hands dirty and create the user
and country
schemas:
First import string
number
optional
InRelation
and OutRelation
from lesan
:
import {
InRelation,
lesan,
MongoClient,
number,
optional,
OutRelation,
string,
} from "https://deno.land/x/lesan@vx.x.x/mod.ts";
and then create the schema shapes:
const userPure = {
name: string(),
address: optional(string()),
age: number(),
};
const countryPure = {
name: string(),
description: string(),
};
const userInRel: Record<string, InRelation> = {
country: {
schemaName: "country",
type: "one",
optional: false,
},
};
const userOutRel = {};
const countryInRel: Record<string, InRelation> = {};
const countryOutRel: Record<string, OutRelation> = {
users: {
schemaName: "user",
number: 50,
sort: { field: "_id", order: "desc", type: "objectId" },
},
};
We should set the schema in coreApp
:
const users = coreApp.odm.setModel("user", userPure, userInRel, userOutRel);
const countries = coreApp.odm.setModel(
"country",
countryPure,
countryInRel,
countryOutRel,
);
At this point, we need to have some endpoints to call from an HTTP request, so let’s write some endpoints.
For creating an end point, we need to set act
from coreApp.acts.setAct
function which requires type
schema
actName
validator
and fn
.
- The
type
is just an enum of two possible options namely,static
anddynamic
. schema
is the name of the model to which we want to set an action.actName
is just a simple string to identify theact
.validator
is a superstructobject
which is called beforeact
fn
calling and validation the giving data.validator
includesset
andget
objects.fn
is the function we call when a request for it arrives.
The following is an one example of act
:
Before creating act
, import object
and ActFn
from lesan
:
import {
ActFn,
InRelation,
lesan,
MongoClient,
number,
object,
optional,
OutRelation,
string,
} from "https://deno.land/x/lesan@vx.x.x/mod.ts";
and the act
will be in the following form:
const addUserValidator = () => {
return object({
set: object(userPure),
get: coreApp.schemas.selectStruct("user", { country: 1 }),
});
};
const addUser: ActFn = async (body) => {
const createdUser = await users.insertOne(body.details.set);
return await users.findOne({ _id: createdUser }, body.details.get);
};
coreApp.acts.setAct({
type: "dynamic",
schema: "user",
actName: "addUser",
validator: addUserValidator(),
fn: addUser,
});
The last thing we need is just to run the web server:
coreApp.runServer({ port: 8080, typeGeneration: true, playground: false });
When typeGeneration
is set to true
, it creates a declarations folder with some typescript typings we need in the project.
Now run this command in the terminal:
deno run -A mod.ts
We are all set and now we can send a POST
HTTP request to http://localhost:8080/lesan
, include the following in JSON
format inside the body in order to retrieve the desired data:
{
"contents": "dynamic",
"wants": {
"model": "user",
"act": "addUser"
},
"details": {
"set": {
"name": "Seyyedeh Sare Hosseini",
"address": "Iran, Hamedan",
"age": 5
},
"get": {
"age": 1,
"address": 1
}
}
}
The working of projection
for retrieving data is fundamentally based on MongoDb Projection.
The coreApp.schemas.selectStruct
function can limit the projection based on your schema relationships and prevent an infinite loop in retrieving data.
After running the server with typeGeneration
set to true, the declarations
folder is created and you can import userInp
from generated type and make coreApp.schemas.selectStruct<userInp>("user", { country: 1 })
type safe:
import { userInp } from "./declarations/selectInp.ts";
const addUserValidator = () => {
return object({
set: object(userPure),
get: coreApp.schemas.selectStruct<userInp>("user", { country: 1 }),
});
};
The following is the full example of what we have discussed so far:
import {
ActFn,
InRelation,
lesan,
MongoClient,
number,
object,
optional,
OutRelation,
string,
} from "https://deno.land/x/lesan@vx.x.x/mod.ts";
const coreApp = lesan();
const client = new MongoClient();
await client.connect("mongodb://localhost:27017/arc");
const db = client.database("core");
coreApp.odm.setDb(db);
const userPure = {
name: string(),
address: optional(string()),
age: number(),
};
const countryPure = {
name: string(),
description: string(),
};
const userInRel: Record<string, InRelation> = {
country: {
schemaName: "country",
type: "one",
optional: false,
},
};
const userOutRel = {};
const countryInRel: Record<string, InRelation> = {};
const countryOutRel: Record<string, OutRelation> = {
users: {
schemaName: "user",
number: 50,
sort: { field: "_id", order: "desc", type: "objectId" },
},
};
const users = coreApp.odm.setModel("user", userPure, userInRel, userOutRel);
const countries = coreApp.odm.setModel(
"country",
countryPure,
countryInRel,
countryOutRel,
);
const addUserValidator = () => {
return object({
set: object(userPure),
get: coreApp.schemas.selectStruct("user", { country: 1 }),
});
};
const addUser: ActFn = async (body) => {
const acts = coreApp.acts.getAtcsWithServices();
const createdUser = await users.insertOne(body.details.set);
return await users.findOne({ _id: createdUser }, body.details.get);
};
coreApp.acts.setAct({
type: "dynamic",
schema: "user",
actName: "addUser",
validator: addUserValidator(),
fn: addUser,
});
const addCountryValidator = () => {
return object({
set: object(countryPure),
get: coreApp.schemas.selectStruct("country", { users: 1 }),
});
};
const addCountry: ActFn = async (body) => {
const createdCountry = await countries.insertOne(body.details.set);
return await countries.findOne({ _id: createdCountry }, body.details.get);
};
coreApp.acts.setAct({
type: "dynamic",
schema: "country",
actName: "addCountry",
validator: addCountryValidator(),
fn: addCountry,
});
coreApp.runServer({ port: 8080, typeGeneration: true, playground: false });
Microservice Architecture with Lesan:
Lesan provides the capability to create independent services which follow the distributed architecture for your system.
Follow the below instructions in order to create a microservice example:
Move the mod.ts
file to core/mod.ts
and create another file in ecommerce/mod.ts
and place the following code in it:
import {
ActFn,
InRelation,
lesan,
MongoClient,
number,
object,
optional,
OutRelation,
string,
} from "https://deno.land/x/lesan@vx.x.x/mod.ts";
const ecommerceApp = lesan();
const client = new MongoClient();
await client.connect("mongodb://localhost:27017/arc");
const db = client.database("core");
ecommerceApp.odm.setDb(db);
const warePure = {
name: string(),
brand: optional(string()),
price: number(),
};
const wareTypePure = {
name: string(),
description: string(),
};
const wareInRel: Record<string, InRelation> = {
wareType: {
schemaName: "wareType",
type: "one",
},
};
const wareOutRel = {};
const wareTypeInRel: Record<string, InRelation> = {};
const wareTypeOutRel: Record<string, OutRelation> = {
wares: {
schemaName: "ware",
number: 50,
sort: { field: "_id", order: "desc" },
},
};
const wares = ecommerceApp.odm.setModel(
"ware",
warePure,
wareInRel,
wareOutRel,
);
const wareTypes = ecommerceApp.odm.setModel(
"wareType",
wareTypePure,
wareTypeInRel,
wareTypeOutRel,
);
const addWareValidator = () => {
return object({
set: object(warePure),
get: ecommerceApp.schemas.selectStruct("ware", { country: 1 }),
});
};
const addWare: ActFn = async (body) => {
const createdWare = await wares.insertOne(body.details.set);
return await wares.findOne({ _id: createdWare }, body.details.get);
};
ecommerceApp.acts.setAct({
type: "dynamic",
schema: "ware",
actName: "addWare",
validator: addWareValidator(),
fn: addWare,
});
const addWareTypeValidator = () => {
return object({
set: object(wareTypePure),
get: ecommerceApp.schemas.selectStruct("wareType", 2),
});
};
const addWareType: ActFn = async (body) => {
const createdWareType = await wareTypes.insertOne(body.details.set);
return await wareTypes.findOne({ _id: createdWareType }, body.details.get);
};
ecommerceApp.acts.setAct({
type: "dynamic",
schema: "wareType",
actName: "addWareType",
validator: addWareTypeValidator(),
fn: addWareType,
});
ecommerceApp.runServer({ port: 8585, typeGeneration: true, playground: false });
Now we have to create servers, one for the core
on port: 8080
and another server for ecommerce
on port: 8585
.
Then let’s implement ecommerce
as a microservice in core
. It’s can be done quitely easy by just adding this lines of code before coreApp.runServer(...)
.
coreApp.acts.setService("ecommerce", "http://localhost:8585/lesan");
Now execute deno run -A mod.ts
in both of core/
and ecommerce/
folders until you could see the following message in your terminal:
on core/
:
HTTP webserver running. Access it at: http://localhost:8080/
and on ecommerce/
:
HTTP webserver running. Access it at: http://localhost:8585/
You can now send an HTTP POST
request for adding wareType which belongs to the ecommerce
service on the http://localhost:8585/lesan
endpoint with the following JSON
in the request body:
{
"contents": "dynamic",
"wants": {
"model": "wareType",
"act": "addWareType"
},
"details": {
"set": {
"name": "digital",
"description": "digital product include phone and ..."
},
"get": {
"name": 1
}
}
}
And even add wareType by sending an HTTP POST
request to http://localhost:8080/lesan
which is for core
service with this JSON
on request body :
{
"service": "ecommerce",
"contents": "dynamic",
"wants": {
"model": "wareType",
"act": "addWareType"
},
"details": {
"set": {
"name": "digital",
"description": "digital product include phone and ..."
},
"get": {
"name": 1
}
}
}
and even better you can export all ecommerce
actions with just one line of code. Thus, add the below code before ecommerceApp.runServer(...)
in ecommerce/mod.ts
and comment the runServer
line.
export const ecommerceActs = ecommerceApp.acts.getMainActs();
// ecommerceApp.runServer({ port: 8585, typeGeneration: true, playground: false });
Now import ecommerceActs in core/mod.ts
:
import { ecommerceActs } from "../ecommerce/mod.ts";
and change coreApp.acts.setService
to :
coreApp.acts.setService("ecommerce", ecommerceActs);
Now we have all the ecommerce actions, even without running the ecommerce server and sending addWareType
request to the core
service for creating wareType
.
If you want to see your actions, simply use this line of code anywhere in your code:
const acts = coreApp.acts.getAtcsWithServices();
console.log();
console.info({ acts }, " ------ ");
console.log();