Our team strongly recommend to use Typeorm.
For our point of view this lib helps us to keep our code identical on each project
and independent to chosen DB
Better to use
uuidinstead of integer value. Because in that case you don't give a user additional info about amount of records in table.
@PrimaryGeneratedColumn("uuid")
id: string;We prefer to structure our code via
BaseEntityinheritance. This approach helps you to keep your code clear and more readable.
@Entity()
export default class Blog extends BaseEntity {
@PrimaryGeneratedColumn("uuid")
id: string;
@Column()
title: string;
@ManyToOne(() => User, (user: User) => user.blog, {
onDelete: 'CASCADE',
})
@JoinColumn()
author: User;
@OneToMany(() => Post, (post: Post) => post.blog, {
cascade: true,
})
posts: Post[];
}// blog.service.ts
class BlogService {
async findAll() {
const blogs = await Blog.find();
// Do something else...
return blogs;
}
}There are many cases when default behavior of Typeorm model is not enough for you. I that case you can just extend that.
import { validateOrReject } from 'class-validator';
import { BaseEntity, BeforeInsert, BeforeUpdate } from 'typeorm';
export default class BaseEntityEnchanted extends BaseEntity {
async createAndDoSomethingElse(data) {
const created = this.create(data);
// You can log, mutate, or do everything you want there
return created;
}
@BeforeInsert()
@BeforeUpdate()
async validate() {
try {
await validateOrReject(this);
} catch (e) {
throw e;
}
}
}You could use
class-validatorin combination with extended
BaseEntityto handle autovalidation of your models (check previous step of the guide).
import BaseEntityEnchanted from 'path-to-file'
import {
Contains,
Length,
IsEmail,
IsFQDN,
Min,
Max,
} from 'class-validator';
@Entity()
export class Post extends BaseEntityEnchanted {
@PrimaryGeneratedColumn("uuid")
id: string;
@Column()
@Length(10, 20)
title: string;
@Column()
@Contains('hello')
text: string;
@Column()
@Min(0)
@Max(10)
rating: number;
@IsInt()
@IsEmail()
email: string;
@Column()
@IsFQDN()
site: string;
}This combination of enhanced class and
class-validatorallows you
to autovalidate your models on save/create etc.
We recommend to create a function for router initialization.
import userRouter from './user';
import shopRouter from './shop';
import blogRouter from './blog';
const createApiRouter = (app) => {
app.use('/api/user', userRouter);
app.use('/api/shop', shopRouter);
app.use('/api/blog', blogRouter);
}
export default createApiRouter;// main file
import 'reflect-metadata';
import { createConnection } from 'typeorm';
import * as express from 'express';
import * as bodyParser from 'body-parser';
import HTTPLogger from './middlewares/logger';
import createApiRouter from './routes';
import createAppErrorListener from './error-handler/app-error-listener';
const app = express();
app.use(HTTPLogger);
app.use(bodyParser.json());
createApiRouter(app);
createAppErrorListener(app);
createConnection().then(() => {
app.listen({ port: 4000 }, () =>
console.log('Now browse to http://localhost:4000'),
);
});Use express
Routerinstead off directly insert routes to app file.
import { Shop } from './../entity/Shop';
import { Router } from "express";
const router = Router();
router.get('/', async (_, res) => {
const allShops = await Shop.find({relations: ['type']});
res.send(allShops);
});
router.post('/', async (req, res) => {
const createdShop = await Shop.save(req.body);
res.send(createdShop);
})
export default router;Store your logic inside of routes it is not a good idea.
You should move that to your controllers.
import { ShopController } from './../controllers';
const router = Router();
router.get('/', ShopController.findAll);
router.post('/', ShopController.create);
export default router;NestJs - is a standard which our company highly recommend to use. This framework helps you to keep your code style the same for all devs on a project.
Also it provides a lot of concepts out form the box which helps us to save time on dev process.
Better to separate DB communication logic from your controller.
import { Injectable } from '@nestjs/common';
import User from 'src/entity/User';
@Injectable()
export default class UserService {
async findAll() {
const users = await User.find();
return users;
}
}Use guards to implement different roles of users or auth.
If you don't have ability to use Nest, your can write middlewares for that.
import { AuthGuard } from '../shared/auth.guard';
import { BoardService } from './board.service';
@Logger('Board Controller')
@Controller('api/boards')
export class BoardController {
constructor(private boardService: BoardService) { }
@Get()
@UseGuards(new AuthGuard())
showAllIBoards(@Query('page') page: number, @User('id') user) {
return this.boardService.showAll(page, user);
}
}