@ibaraki-douji/api
v2.1.10
Published
Create a simple API server with TypeScript.
Downloads
42
Maintainers
Readme
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)