2.1.10 • Published 9 months ago

@ibaraki-douji/api v2.1.10

Weekly downloads
-
License
ISC
Repository
-
Last release
9 months ago

API

Easy make REST API with layers and dynamic routes.

⚠️ This project need a specific structure to work and only work in typescript (or gl with js files)! ⚠️

- project
| - src
| - | - index.ts
| - | Controllers
| - | - | - UserController.ts
| - | Services
| - | - | - UserService.ts
| - | Mappings
| - | - | - UserMapping.ts
| - | Repositories
| - | - | - UserRepository.ts
| - | Models
| - | - | - User.ts
| - package.json

Getting started

npm i @ibaraki-douji/api --save

Usage

Create the API

This is the content of the index.ts file.

import API from "@ibaraki-douji/api";

API.debug = true;
API.start(3000);

Replace 3000 by your port. (default: 5055)

Create Controller

All controllers need to be placed in the Controllers folder.

import { Autowire, BodyParam, Controller, Delete, Get, PathParam, Post, Put } from "@ibaraki-douji/api";
import { UserService } from "../Services/UserService";
import { User } from "../Models/User";

@Controller('/users')
export class UserController {

    @Autowire()
    private userService: UserService;

    constructor() {}

    @Get('/:id')
    public getOne(
        @PathParam("id") id: number
    ) { 
        return this.userService.getUser(id);
    }

    @Get('/')
    public async getAll() {
        return this.userService.getUsers();
    }

    @Post('/')
    public async create(
        @BodyParam() user: User
    ) {
        return this.userService.createUser(user);
    }

    @Put('/:id')
    public async update(
        @PathParam("id") id: number,
        @BodyParam() user: User
    ) {
        return this.userService.updateUser(id, user);
    }

    @Delete('/:id')
    public async delete(
        @PathParam("id") id: number
    ) {
        return this.userService.deleteUser(id);
    }

}

Create Service

All services need to be placed in the Services folder.

import { Autowire, Service } from "@ibaraki-douji/api";
import { UserRepository } from "../Repositories/UserRepository";
import { User } from "../Models/User";

@Service()
export class UserService {

    @Autowire()
    private userRepository: UserRepository;

    public async getUser(id: number) {
        return this.userRepository.get(id);
    }

    public async getUsers() {
        return this.userRepository.getAll();
    }

    public async createUser(user: User) {
        return this.userRepository.create(user);
    }

    public async updateUser(id: number, user: User) {
        return this.userRepository.update(id, user);
    }

    public async deleteUser(id: number) {
        return this.userRepository.delete(id);
    }
}

Create Repository

All repositories need to be placed in the Repositories folder.

import { Repository, RepositoryImpl } from "@ibaraki-douji/api";
import { User } from "../Models/User";

@Repository("users")
export class UserRepository extends RepositoryImpl<User> {

}

Create Model

All models need to be placed in the Models folder.

import { Id, Model, Role } from "@ibaraki-douji/api";

@Model()
export class User {

    @Id()
    id: number;
    name: string;
    email: string;
    password: string;

    @Role()
    role: string;
}

Create Mapping

All mappings need to be placed in the Mappings folder.

import { Service } from "@ibaraki-douji/api";

@Service()
export class UserMapping {

    public map(user: any) {
        return {
            id: user.id,
            name: user.name,
            email: user.email,
            role: user.role,
            created_at: user.created_at
        }
    }

    public mapAll(users: any[]) {
        return users.map(user => this.map(user));
    }

}

tsconfig.json

You need to add this to your tsconfig.json file.

{
    "compilerOptions": {
        "rootDir": "./src",
        "outDir": "./lib",
        "noEmitHelpers": false,
        "target": "ESNext",
        "moduleResolution": "node",
        "module": "CommonJS",
        "experimentalDecorators": true,
        "skipLibCheck": true,
        "emitDecoratorMetadata": true,
        "useDefineForClassFields": false
    },
    "include": [
        "./src/**/*"
    ]
}

package.json

You need to add this to your package.json file.

{
    "scripts": {
        "start": "tsc && node ./lib/index.js",
        "build": "tsc"
    },
    "dependencies": {
        "@ibaraki-douji/api": "latest"
    },
    "devDependencies": {
        "@types/node": "latest",
        "typescript": "latest"
    }
}

Use npm run start to start the API. (You might need to change the index.js to the main file of your API)
Use npm run build to build the API.

Documentation

Annotations / Decorators

@Controller

This decorator is used to define a controller.
You can add a prefix to the controller. (ex: /users is the base route for all routes in the controller)
You can name the controller (used for the documentation).

import { Controller } from "@ibaraki-douji/api";

@Controller('/users', { name: "Users" })
export class UserController {
    // ...
}

@Service

This decorator is used to define a service.
This decorator also work with mappings.

import { Service } from "@ibaraki-douji/api";

@Service()
export class UserService {
    // ...
}

@Repository

This decorator is used to define a repository.
You can add a prefix to the repository. (ex: users is the name of the table in the database)

import { Repository, RepositoryImpl } from "@ibaraki-douji/api";

@Repository("users")
export class UserRepository extends RepositoryImpl<User> {
    // ...
}

@Entity / @Model

This decorator is used to define an entity. You can use either @Entity or @Model to define an entity.

import { Entity } from "@ibaraki-douji/api";

@Entity()
export class User {
    // ...
}

@Autowire

This decorator is used to inject a service or a repository in a controller or a service.

import { Autowire, Controller } from "@ibaraki-douji/api";

@Controller('/users')
export class UserController {

    @Autowire()
    private userService: UserService;

    // ...
}

@Model

This decorator is used to define a model.

import { Model } from "@ibaraki-douji/api";

@Model()
export class User {
    // ...
}

@Id

This decorator is used to define the id of a model.
This is used for the authorization/security.

import { Model, Id } from "@ibaraki-douji/api";

@Model()
export class User {

    @Id()
    id: number;

    // ...
}

@Role

This decorator is used to define the role of a model.
This is used for the authorization/security.

import { Model, Role } from "@ibaraki-douji/api";

@Model()
export class User {

    @Role()
    role: string;

    // ...
}

@Get / @Post / @Put / @Delete / @Patch / @Head / @Options

Those decorators are used to define a route.
You can add a prefix to the route. (ex: /:id is the id of the user)

import { Controller, Get, PathParam } from "@ibaraki-douji/api";

@Controller('/users')
export class UserController {

    @Get('/:id')
    public getOne(
        @PathParam("id") id: number
    ) {   
        // ...
    }

    @Get('/:id', { name: 'getOne', response: { property: { type: "string"}}})
    public getOne(
        @PathParam("id") id: number
    ) {   
        // ...
    }
}

You can add a name to the route and / or a description. (used for the documentation)
You can also add a custom request and response to the route. (used for the documentation)
For the custom request and response you can also provide a model. (ex: @Get('/:id', { request: UserModel, response: UserModel}))
For an custom Array request/response you can do @Get('/:id', { request: [UserModel], response: [UserModel]}).

@PathParam / @QueryParam / @HeaderParam / @CookieParam

Those decorators are used to get the parameters of a route.
They all need a name.
For the PathParam, you need to add the name of the parameter in the route.
For the QueryParam, CookieParam and HeaderParam, you need to add the name of the parameter in the query, cookie or in the header.

import { Controller, Get, HeaderParam, CookieParam, PathParam, QueryParam } from "@ibaraki-douji/api";

@Controller('/users')
export class UserController {

    @Get('/:id')
    public getOne(
        @PathParam("id") id: number,
        @QueryParam("name") name: string,
        @HeaderParam("token") token: string,
        @CookieParam("session") session: string
    ) {   
        // ...
    }

    // ...
}

@BodyParam / @HeadersParam / @CookiesParam

Those decorators are used to get the body or all headers of a request.

import { BodyParam, Controller, CookiesParam, HeadersParam, Post, Headers, Cookies } from "@ibaraki-douji/api";

@Controller('/users')
export class UserController {

    @Post('/')
    public async create(
        @BodyParam() user: User,
        @HeadersParam() headers: Headers,
        @CookiesParam() cookies: Cookies
    ) {
        // ...
    }

    // ...
}

@Security

This decorator is used to define a security.
You can add a name to the security. (ex: user is the name of the security group (explained later))
⚠️ You need to put the @Security decorator before the route decorator. ⚠️

import { Controller, Get, PathParam, Security } from "@ibaraki-douji/api";

@Controller('/users')
export class UserController {

    @Security("user")
    @Get('/:id')
    public getOne(
        @PathParam("id") id: number
    ) {   
        // ...
    }

    // ...
}

@WebSocket

This decorator is used to define a websocket route.
You can add a prefix to the route. (ex: /:id is the id of the user)

import { Controller, BodyParam, WebSocket } from "@ibaraki-douji/api";

@Controller('/users')
export class UserController {

    @WebSocket('/:id')
    public messages(
        @BodyParam() id: number
    ) {   
        // ...
    }

    // ...
}

@String / @Number / @Boolean / @Array / @Object

Those decorators are used to define the type of a parameter in a model.

import { Entity, String, Number, Boolean, Array, Object } from "@ibaraki-douji/api";

@Entity()
export class User {

    @String()
    name: string;

    @Number()
    age: number;

    @Boolean()
    isAdult: boolean;

    @Array("string")
    friends: string[];

    @Object({
        street: {
            type: "string"
        },
        city: {
            type: "string"
        },
        country: {
            type: "string"
        }
    })
    address: {
        street: string;
        city: string;
        country: string;
    };

    // ...
}

You can also use a model as a type.

import { Entity, Array, Object } from "@ibaraki-douji/api";

@Entity()
export class User {

    @Array(User)
    friends: User[];

    @Object(User)
    friend: User;
}

For @Array you can do a multi type array by putting your types in an array.

import { Entity, Array, Object } from "@ibaraki-douji/api";

@Entity()
export class User {

    @Array(["string", "number"])
    friends: (string | number)[];
}

Layers

Repository

The repository is used to interact with the database.
You can use the RepositoryImpl class to create a repository.

import { Repository, RepositoryImpl } from "@ibaraki-douji/api";

@Repository("users")
export class UserRepository extends RepositoryImpl<User> {
    // ...
}

You can add more methods to the repository.

import { Repository, RepositoryImpl } from "@ibaraki-douji/api";

@Repository("users")
export class UserRepository extends RepositoryImpl<User> {

    public async getByName(name: string) {
        return this.getAll().find(user => user.name === name);
    }

}

The default methods are :

  • get(id: number)
  • getAll()
  • create(object: T)
  • update(id: number, object: T)
  • delete(id: number)

If you are not happy with the few methods provided by the RepositoryImpl class, you can wire the DBManager class.

import { Repository, DBManager, Autowire } from "@ibaraki-douji/api";

@Repository("users")
export class UserRepository extends RepositoryImpl<User> {

    @Autowire()
    private dbManager: DBManager;

    public async getByName(name: string) {
        return this.dbManager.query("SELECT * FROM users WHERE name = ?", [name]);
    }

    public async setAge(id: number, age: number) {
        return this.dbManager.query("UPDATE users SET age = ? WHERE id = ?", [age, id]);
    }

}

Mapping

Mapping classes are optional. They are used to change the data returned by the API if this is different from the data in the database.

import { Service } from "@ibaraki-douji/api";

@Service()
export class UserMapping {
    // ...
}

Service

The service is used to interact with the repository and the mapping. It is also used to add more logic to the API.

import { Service } from "@ibaraki-douji/api";

@Service()
export class UserService {
    // ...
}

Controller

The controller is used to define the routes of the API.
It uses the service to interact with the database.

import { Controller } from "@ibaraki-douji/api";

@Controller('/users')
export class UserController {
    // ...
}

Database

For now the DB is only MySQL.
To configure the DB you need to set the environment variables :

  • DB_HOST (default: 127.0.0.1)
  • DB_PORT (default: 3306)
  • DB_USER (default: root)
  • DB_PASSWORD (default: root)
  • DB_DATABASE (default: api)

Make sure to have an empty database with the name you set in the environment variables.

Main file

You need to create a main file to start the API.

import API from "@ibaraki-douji/api";

API.start();

Custom layers folders

You can change the folders of the layers in the main file.
The path is from the src folder.

import API from "@ibaraki-douji/api";

API.folders.controllers = "controllers"; // default: "Controllers"
API.folders.services = "services"; // default: "Services"
API.folders.repositories = "repositories"; // default: "Repositories"
API.folders.mappings = "mappings"; // default: "Mappings"
API.folders.entities = "entities"; // default: "Models"

Debug logs

You can enable the debug logs in the main file.

import API from "@ibaraki-douji/api";

API.debug = true; // default: false

API.start();

Port

You can change the port of the API in the main file.

import API from "@ibaraki-douji/api";

API.start(3000); // default: 5055

Websocket

You can enable the websocket in the main file.

import API from "@ibaraki-douji/api";

API.websocket = true; // default: false

API.start();

Swagger

You can enable the swagger in the main file.

import API from "@ibaraki-douji/api";

API.swagger = true; // default: false

API.servers.push({
    baseUrl: "http://localhost:5055",
    name: "Local server"
}) // default: [{baseUrl: "http://localhost:<port>", name: "default"}]

API.start();

Swagger is available at /swagger-ui.
Swagger JSON file is available at /swagger-ui/api.json.


Security

You can configure the security of the API in the main file.

import API from "@ibaraki-douji/api";

// Enable or disable the security
API.security.enabled = true; // default: false

// Configure the secret used to generate the tokens
API.security.secret = "secret"; // default: random string of 32 characters

// Configure if all the routes are protected by default (with minimum level)
API.security.defaultProtected = true; // default: false

// Configure the user model (the model need to have a "role" property and an "id" property)
API.security.userModel = "User"; // default: "User"

// Configure the roles (value is the name of the role (need to be the same as the DB values) and level is the minimum access level)
API.security.roles = [ // default: like this
    {
        value: "user",
        level: 1
    },
    {
        value: "admin",
        level: 2
    }
];

Handle Websocket

Client

Connect to the websocket with the url ws://localhost:5055 (replace localhost and 5055 with your values).

const socket = new WebSocket('ws://localhost:5055');

Send a message to the server.

const data = {
    endpoint: "/something", // the endpoint of the API
    data: "your data", // can be a string, a number, an object, ...
    token: "XXXXXXXXXXXXXXXXXXXXXX" // the token of the user (if endpoint is secured with minimum level)
};

socket.send(JSON.stringify(data));

Receive a message from the server.
if you want to listen to protected routes you need to send the token of the user at least once.4

socket.onmessage = function(event) {
    const data = JSON.parse(event);
    console.log(event.data);
};

Server

You can handle websocket in the controller.

import { Controller, WebSocket, PathParam, BodyParam } from "@ibaraki-douji/api";

@Controller('/users')
export class UserController {

    @WebSocket('/:id')
    public messages(
        @PathParam() id: number,
        @BodyParam() data: any
    ) {   
        // ...
    }

    // ...
}

You can return a value to the client.

import { Controller, WebSocket, PathParam, BodyParam } from "@ibaraki-douji/api";

@Controller('/users')
export class UserController {

    @WebSocket('/:id')
    public messages(
        @PathParam() id: number,
        @BodyParam() data: any
    ) {   
        return "Hello World";
    }

    // ...
}

You can broadcast a message to all the clients. (if protected route, the user need to send the token at least once)

import { Controller, WebSocket, PathParam, BodyParam, Autowire, WebsocketService } from "@ibaraki-douji/api";

@Controller('/users')
export class UserController {

    @Autowire()
    private websocketService: WebsocketService;

    @WebSocket('/:id')
    public messages(
        @PathParam() id: number,
        @BodyParam() data: any
    ) {   
        this.websocketService.broadcast("/users/" + id, "Hello World");
    }

    // ...
}

This will send the message to all the clients connected to the route /users/:id.

If you want to create an endpoint to only send messages to the clients you can do this : (it also can be a protected route)

import { Controller, WebSocket, BodyParam, Autowire, WebsocketService, Post } from "@ibaraki-douji/api";

@Controller('/messages')
export class MessagesController {

    @Autowire()
    private websocketService: WebsocketService;

    @Listener()
    @WebSocket('/')
    public messages() {}

    @Post('/')
    public sendMessage(
        @BodyParam() data: any
    ) {
        this.websocketService.broadcast("/messages", data);
    }
    // ...
}

Retrive the token of the user.

import { Controller, WebSocket, BodyParam, Autowire, WebsocketService, Post } from "@ibaraki-douji/api";

@Controller('/messages')
export class MessagesController {

    @Autowire()
    private websocketService: WebsocketService;

    @Listener()
    @WebSocket('/')
    public messages(
        @BodyParam() data: any,
        @Header("authorization") token: string
    ) {
        console.log(token.split(" ")[1]);
    }

    // ...
}

Start the API

To start the API you need to run the index.js file.

node lib/index.js

You can also setup this command in your package.json file.

{
    "scripts": {
        "start": "node lib/index.js"
    }
}

Then you can run the API with :

npm start

More Help and Support

Discord : https://discord.gg/mD9c4zP4Er (Projects category then help-me channel)

2.1.9

9 months ago

2.1.10

9 months ago

2.0.0-beta.9

12 months ago

2.0.0-beta.8

12 months ago

2.0.0-beta.7

12 months ago

2.1.2

11 months ago

2.0.3

11 months ago

2.1.1

11 months ago

2.0.2

12 months ago

2.1.4

11 months ago

2.1.3

11 months ago

2.1.6

11 months ago

2.1.5

11 months ago

2.1.8

11 months ago

2.1.7

11 months ago

2.0.0-beta.2

12 months ago

2.0.0-beta.1

12 months ago

2.0.0-beta.6

12 months ago

2.0.0-beta.5

12 months ago

2.1.0

11 months ago

2.0.1

12 months ago

2.0.0-beta.4

12 months ago

2.0.0

12 months ago

2.0.0-beta.3

12 months ago

1.1.1

3 years ago

1.1.4

3 years ago

1.1.3

3 years ago

1.1.2

3 years ago

1.1.0

3 years ago

1.0.0

3 years ago