@enoviah/nest-core
v1.2.0
Published
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Downloads
2
Readme
NestJs
Created At: Jan 20, 2021 10:55 AM Destination: common Environnement: Outworld-QA Last Update: Feb 3, 2021 11:03 AM Responsable: Arthur Pain Type: Documentation Univers: Technique
1 - Guideline
Par défaut, Nest se base sur le système d'Angular. Ce qui implique que vous pouvez utiliser des modules, des providers, des controllers et des services.
Nest propose sont propre système d'injection semblable à celui d'Angular.
Vous pouvez accéder à la documentation ici.
1.1 - Structure
Voici la structure à garder en tête lors de la création d'une api. Elle peut être modifié en fonction des besoins. Mais pour un soucis de maintenabilité, veuillez la garder au maximum semblable à celle-ci.
--| src
--| modules
--| users
--| users.module.ts // Global module
--| users.controller.ts // User controller (All access POST, GET, ...)
--| user.providers.ts // User provider (Provide user Schema)
--| user.service.ts // User method (Controller should call the service)
--| schema
--| user.schema.ts // Define user schema for mongoDb
--| models
--| user.dto.ts // Define all DTO of user
--| user.model.ts // Define user model & user Document
--| environment
--| providers
--|
--| main.ts
--| app.module.ts
--| config.ts
2 - Configuration
Par défaut, vous devez appliquer le guide Typescript à votre api NestJs.
Votre api doit obligatoirement utiliser le modules @enoviah/nest-core
$> npm install --save @enoviah/nest-core:latest
3 - Nest-Core
Nest-core est le module interne à Outworld qui surcouche nestJs pour proposer différentes features.
3.1 - Environnement
Le module nest-core expose une gestion de l'environnement. Par défaut, dotenv est utilisé pour charger l’environnement.
3.1.1 - Chargement de l'environnement
Voici l'ordre de priorité:
- .env
- .env.example
- environnement système
Si le .env n'existe pas, le .env.example sera chargé. Si il n'existe pas, l'environnement du système sera utilisé.
3.1.2 Configuration
src/environment/ Location des fichiers liés à l'environnement
env.model Model de l'environnement
interface Environment {
PORT: string;
}
export default Environment;
env.ts Export de l'environnement
import { EnvironmentService } from '@enoviah/nest-core';
import Environment from './env.model';
const environment = new EnvironmentService<Environment>();
environment.loadEnvironment(false);
export default environment;
Optionnel env.ts Validation de l'environnement avec un schéma
import { EnvironmentService } from '@enoviah/nest-core';
import Environment from './env.model';
import envSchema from './env.schema';
const environment = new EnvironmentService<Environment>();
environment.validators = envSchema; // Assignation du schéma
environment.loadEnvironment(true);
export default environment;
Optionnel env.schema.ts JsonSchema de l'environnement
import { Schema } from 'jsonschema';
const envSchema: Schema = {
id: '/Env',
type: 'object',
properties: {
PORT: { type: 'string' },
},
required: ['PORT'],
};
export default envSchema;
3.1.3 - Utilisation
Example d'utilisation
/src/main.ts
import environment from './env/env';
console.log(environment.environment.PORT);
3.2 CRUD operations
Ce module expose un service qui va gérer les opérations basiques d'un controller (Create, Read, Update, Delete).
La class CRUD expose par défaut:
- create(): Create document
- findMany(): Find documents from query (Voir queries automatique)
- findOne(): Find a document
- updateOne(): Update a document
- deleteOne(): Delete a document
user.service.ts
import { CRUD } from '@enoviah/nest-core';
import { Document, Model } from 'mongoose';
import { User, UserDocument } from './models/user.model';
import { Inject } from '@nestjs/common';
import { matchField } from '@enoviah/nest-core';
export class UsersService extends CRUD<User> {
constructor(@Inject('USER_MODEL') private readonly usersModel: Model<UserDocument>) {
super(usersModel);
this.queryList.push({
matcher: ['status'],
handler: (key: string, value: string) => {
return matchField({ status: value });
},
priority: 100,
});
}
}
3.3 - MongoDb
Nest-core expose un module de base de donnée
3.3.1 - Environnement
Vous devez définir votre environnement avec les variables demandées par le module:
MONGO_DATABASE=myDb
MONGO_ADDRESS=mongodb:27017
MONGO_USER=default // Do not use if auth is not defined on your database
MONGO_PASSWORD=default // Do not use if auth is not defined on your database
3.3.2 - Utilisation
Exemple d'utilisation pour la collection Users.
/src/modules/users
user.schema.ts Définition du schéma
import { Schema } from 'mongoose';
import ObjectId = Schema.Types.ObjectId;
const UserSchema = new Schema({
firstName: { type: String, required: true },
lastName: { type: String, required: true },
email: { type: String, required: true },
authId: { type: ObjectId, required: true },
}, { timestamps: true });
export default UserSchema;
user.providers.ts
import { Provider } from '@nestjs/common';
import { Connection } from 'mongoose';
import UserSchema from './user.schema';
const providers: Provider[] = [{
provide: 'USERS_MODEL',
useFactory: (connection: Connection) => connection.model('Users', UserSchema),
inject: ['DATABASE_CONNECTION'],
}];
export default providers;
user.module.ts
import {
DatabaseModule,
} from '@enoviah/nest-core';
import { Module } from '@nestjs/common';
import userProviders from './users.providers';
import UsersService from './users.service';
@Module({
imports: [DatabaseModule],
controllers: [],
providers: [...userProviders, UsersService],
exports: [UsersService],
})
class UsersModule {
}
export default UsersModule;
user.service.ts
import {Model} from 'mongoose';
import {
Inject,
Injectable,
} from '@nestjs/common';
@Injectable()
class UsersService {
constructor(@Inject('USERS_MODEL') private readonly userModel: Model<UserDocument>) {
}
}
3.4 - Queries automatique
Vous pouvez utiliser le système de queries automatique.
Il y a deux modes :
- Pagination
- Sans pagination
Ces deux modes peuvent être activé grâce à l'objet de configuration.
Par défaut, la class Query expose deux queries:
- limit (Limite des résultats)
- offset (Début des résulats)
Si vous utilisez le mode pagination et que vous envoyez une de ces deux queries, elles sera appliquée.
Exemple avec une query possible sur la key firstName de User.
user.service.ts
import {Model} from 'mongoose';
import {
Inject,
Injectable,
} from '@nestjs/common';
import { ExtractedQueries, Query, Stage, Utils, matchField } from '@enoviah/nest-core';
@Injectable()
class UsersService extends Query {
constructor(@Inject('USERS_MODEL') private readonly userModel: Model<UserDocument>) {
this.queryList.push({
matcher: ['firstName'],
handler: async (key: string, value: string) => {
const search = new RegExp('^' + value, 'ig');
return matchField({ firstName: search });
},
priority: 1,
});
}
// Mode pagination
getUsers(queries: Record<string, string>): Observable<{results: User[], count: number}> {
return this.extractQuery(queries, {count: true)
.pipe(mergeMap((queries: ExtractedQueries[]) => {
return from(this.userModel.aggregate([queries]).exec()).pipe(Utils.mapAggregateCount())
};
}));
}));
}
// Mode sans pagination
getUsers(queries: Record<string, string>): Observable<User[]> {
return this.extractQuery(queries, {})
.pipe(mergeMap((queries: ExtractedQueries[]) => {
return from(this.userModel.aggregate([queries]).exec());
};
}));
}));
}
}
3.5 - Validation de données
Nest-core propose un service de validation de donnée basé sur Json-Schema.
user.module.ts
import {
DatabaseModule,
JsonSchemaModule,
} from '@enoviah/nest-core';
import { Module } from '@nestjs/common';
import userProviders from './users.providers';
import UsersService from './users.service';
@Module({
imports: [DatabaseModule, JsonSchemaModule],
controllers: [],
providers: [...userProviders, UsersService],
exports: [UsersService],
})
class UsersModule {
}
export default UsersModule;
user.service.ts
import {
AuthUser,
BadRequest,
JsonSchemaService,
} from '@enoviah/nest-core';
import {
BadRequestException,
Inject,
Injectable,
} from '@nestjs/common';
import {
Model,
} from 'mongoose';
import {
catchError,
mergeMap,
} from 'rxjs/operators';
import { UserDocument } from '../../models/user/user.document';
import CreateUserRequest from '../../models/user/user.dto';
import creatUserSchema from '../../models/user/user.validations';
@Injectable()
class UsersService {
constructor(@Inject('USERS_MODEL') private readonly userModel: Model<UserDocument>,
private readonly jsonSchemaService: JsonSchemaService) {
}
create(body: CreateUserRequest): Observable<UserDocument> {
// Will automatically throw a 400 bad request if the validation fail
this.jsonSchemaService.validate(body, creatUserSchema, true);
}
}
export default UsersService;
3.6 - Gestion d'erreur
Nest-core propose une gestion des exceptions via un filtre.
Il agira sur les exceptions et les transformeras en une réponse http formatée.
main.ts
**import { NestExpressApplication } from '@nestjs/platform-express';
import { EnvStatus } from '@enoviah/nest-core/dist/models/environment.model';
import { NestFactory } from '@nestjs/core';
import * as cors from 'cors';
import * as bodyParser from 'body-parser';
import { HttpExceptionFilter, Utils } from '@enoviah/nest-core';
import * as helmet from 'helmet';
import { Logger } from '@nestjs/common';
import AppModule from './app.module';
import environment from './environment/env';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
app.use(helmet());
app.use(cors({ origin: '*' }));
app.use(bodyParser.json());
// Register the filter
app.useGlobalFilters(new HttpExceptionFilter());
Logger.debug(`API listening on ${environment.environment.PORT}`);
await app.listen(+environment.environment.PORT);
}
bootstrap();**
Si j'ai le code suivant:
app.controller.ts
import {
Controller,
Get
} from '@nestjs/common';
import {
BadRequest,
} from '@enoviah/nest-core';
@Controller('/')
class AppController {
constructor() {
}
@Get('/500')
async getAccount() {
throw new Error('Oops');
}
@Get('/400')
async getAccount() {
throw new BadRequest({ message: 'Passwords missmatch', code: 'EPASSWORDMISSMATCH' }); }
}
export default AccountController;
les réponses http seront les suivantes:
// /500
{
"message": "Oops",
"status": 500,
"code": "EINTERNALESERVERERROR",
}
// /400
{
"message": "Passwords missmatch",
"code": "EPASSWORDMISSMATCH",
"status": 400,
}
Une erreur inconnue sera aussi traitée par l’intercepteur et sera transformée en 500.
3.7 - Resources
Pour uniformiser la géstion des images / Vidéos, nest-core propose un module de gestion de ces derniers.
3.7.1 - Philosophie
Une resource est un document contenu dans la collection Resources. Il représente un fichier (qu'importe l'extension) stocké à un emplacement (Google Storage, Local, ...).
Ce module vous permet d'upload un fichier, de valider son contenu et de pouvoir générer l'url d'accés à n'importe quel moment.
3.7.2 - Utilisation
Rentrons dans le vif du sujet.
D'abord, il faut définir les variables d’environnements suivantes :
.env
UPLOAD_LIMIT=1250000 // Taille max d'un fichier
UPLOAD_DIR=media // Dossier dans lequel les fichiers seront mis (/public/media)
Ensuite, il faut utiliser le module qui va exposer les router des resources
app.module.ts
import { Module } from '@nestjs/common';
import { NestCoreModule, GmapsModule, ResourceModule, Utils } from '@enoviah/nest-core';
@Module({
imports: [
NestCoreModule,
ResourceModule,
],
controllers: [],
providers: [],
})
export class AppModule {
}
Si vous avez bien configurer votre environnement, vous pouvez maintenant utiliser les routes. Voir l'api-docs pour la liste.
Si vous voulez pouvoir accéder aux resources via une url, il faut ajouter la ligne suivante:
main.ts
function bootstrap() {
...
app.useStaticAssets(join(__dirname, 'public'));
}
Example pour un utilisateur
user.schema.ts
import { Schema } from 'mongoose';
import ObjectId = Schema.Types.ObjectId;
export const UserSchema = new Schema({
firstName: { type: String },
lastName: { type: String },
phoneNumber: { type: String },
email: { type: String },
avatar: {type: ObjectId, ref: 'Resources'} // On indique au schéma que c'est une ressource
}, { timestamps: true });
user.controller.ts
import { Body, Controller, Get, Param, Post, Put, Req, UseGuards } from '@nestjs/common';
import { UserService } from './user.service';
import { User } from './model/user.model';
import {
AuthenticatedRequest,
BearerGuard,
InternalErrorResponse,
Roles,
RolesGuard,
UnauthorizedErrorResponse,
} from '@enoviah/nest-core';
@Controller('users')
@ApiTags('Users')
export class UserController {
constructor(private readonly userService: UserService) {
}
@Get(':id')
@UseGuards(BearerGuard)
@UseInterceptors(ResourceInterceptor) // On utilise l'interceptor des resources
@SetMetadata('resources', ['avatar']) // on indique ou se trouve la resource dans l'objet
updateOne(@Param('id') id: string) {
return this.userService.findOne(id).pipe(mergeMap((user) => {
return from(user.populate('avatar').execPopulate());
}));
}
}
On obtient après cet appel :
GET /USERS/:id
{
...,
"avatar": {
"_id": "string",
"url": "https://xxxx/public/resources/xxx.png",
}
}
3.7.3 - Resource service
Vous pouvez accéder à ResourceService pour directement agir sur une resource.
3.8 - Google maps
Vous pouvez utiliser le controller Google maps de nest-core.
Il vous permet de :
- D'auto-compléter une adresse en France
- D'obtenir les coordonnées d'une adresse
- D'obtenir les informations d'une place
- D'obtenir les places autour d'une coordonnée
3.8.1 - Utilisation
Comme pour les autres modules, il vous suffit de l'importer dans votre module et de définir votre clef google.
Attention: Pensez à activer les différentes API sur votre panel Google.
.env
GMAPS_API_KEY=azeazxxxxazefezfzefez-azes
app.module.ts
import { Module } from '@nestjs/common';
import { NestCoreModule, GmapsModule, ResourceModule, Utils } from '@enoviah/nest-core';
@Module({
imports: [
NestCoreModule,
GmapsModule,
],
controllers: [],
providers: [SyncGateway],
})
export class AppModule {
}
Vous pouvez maintenant utiliser les différentes routes.