/lesan

New way to create web server and NoSQL data model

Primary LanguageTypeScriptGNU Affero General Public License v3.0AGPL-3.0

بسم الله الرحمن الرحیم

Why Lesan?

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.

This video is an introductory tutorial on Lesan framework in Farsi language.

Benchmarks

benchmark-animation

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 the prisma-express-rest. Which uses postgres as a database.
  • Lesan return data to client 1417% faster than the prisma-express-graphql. Which uses postgres as a database.
  • Lesan return data to client 4435% faster than the mongoose-express-rest (Note that we did not sort in this query)
  • Lesan return data to client 72289% faster than the mongo-express-rest (Note that we did not sort in this query)
  • Lesan return data to client 298971% faster than the mongoose-express-rest (used sortby)

Maybe we created the most performant framework in the world! see more detailed benchmark

Documantation

A little trip

Look below code:

Create a file called mod.ts and paste the code below into it:

import {
  ActFn,
  Document,
  Filter,
  lesan,
  MongoClient,
  number,
  object,
  ObjectId,
  optional,
  size,
  string,
} from "https://deno.land/x/lesan@vx.x.x/mod.ts"; // Please replace `x.x.x` with the latest version in [releases](https://github.com/MiaadTeam/lesan/releases)

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 ==================
// ------------------ Country Founctions ------------------
// ------------------ 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,
});

// ------------------ User Founctions ------------------
// --------------------- 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,
});

// ================== RUM SECTION ==================
coreApp.runServer({ port: 1366, typeGeneration: false, playground: true });

Please replace x.x.x in the import link with the latest version in releases

Now run this command in the terminal:

deno run -A mod.ts

You should see this messsage:

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. Screen Shot 1402-04-26 at 20 47 05

alternativly you can send post request to http://localhost:1366/lesan with postman include 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
    }
  }
}

Screen Shot 1402-04-25 at 18 24 16

We handle all relationships between the data and embed everything. You can also control the level of penetration into the action get depth. On the client-side, you can describe what you want and get back exactly what you described.

Contributors

Many thanks to those who supported us

Mehrshad Cheshm Khavari
Mehrshad Cheshm Khavari
Mahdi Ramezani
Mahdi Ramezani
Alireza Safaierad
Alireza Safaierad
ali davodi
ali davodi
sorena ganji
sorena ganji
Node Master
Node Master
Erfan Asadi
Erfan Asadi