1.3.2 • Published 2 years ago

@outworld2/nest-core v1.3.2

Weekly downloads
-
License
-
Repository
-
Last release
2 years ago

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

Build Status

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é:

  1. .env
  2. .env.example
  3. 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

Par défaut, Le module de bdd essaiera de vous connecter 3 fois. Si il ne parvient pas à établir une connexion, une erreur sera jeté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/app.module.ts

import { DatabaseModule } from '@enoviah/nest-core';
import { Module } from '@nestjs/common';

@Module({
  imports: [DatabaseModule.forRoot()],
  controllers: [],
  providers: [],
})
class AppModule {
}

export default AppModule;

/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: [],
  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.