Arquitectura de aplicación basada en NestJs Framwork
Para poder entender mejor la arquitectura debería leer primero la documentación disponible del framework en https://docs.nestjs.com/
Nombres de carpetas internas.
Entity
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
import { IsString, IsEmpty } from 'class-validator';
@Entity() //Indicar que es una entidad
export class Organization {
@PrimaryGeneratedColumn() //Llave primaria autogenerada
@IsEmpty() //Validación por el api rest no se puede pasar este valor
id: number;
@Column({ length: 500 }) //String
@IsString() //Validación por api rest debe ser un string
name: string;
@Column('text')
@IsString()
description: string;
}
import { Component } from '@nestjs/common';
import { Organization } from '../entity/organization';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import {ModelInterface} from '../../base/proxy-model';
@Component() //Indicar a nestjs que es un servicio
export class OrganizationModel implements ModelInterface { //La interfaz ModelInterce se utiliza en ProxyModel
constructor(
@InjectRepository(Organization) private repository: Repository<Organization> //Recibe el repositorio de TypeOrm
) {
}
getRepository() { //Método de ModelInterface
return this.repository;
}
}
@Module({
imports: [TypeOrmModule.forFeature([Organization])], //Informar a TypeOrm de la entidad
components: [
ProxyModel.injectorFactory('Organization'), //Implementar un proxy al model para que se comporte como un repositorio de TypeOrm
OrganizationModel, //Injectar el Model
],
})
export class OrganizationModule {
}
@Controller('organization')
export class OrganizationApi {
constructor(
@Inject('Organization') private model: OrganizationModel
){}
@Get()
async list(): Promise<Organization[]> {
return await this.model.find(); //Método no implementado en el model pero si en el repositorio.
}
}
Se logra usando la clase “ProxyModel” que básicamente lo que hace es devolver un objeto “Proxy” de javascript que hace lo siguiente.
@Controller('organization')
export class OrganizationApi {
constructor(
@Inject('Organization') private model: OrganizationModel //En el constructor recibe las dependencias.
){}
}
@Controller('organization')
export class OrganizationApi {
@Get() //Indica que es método http get
async list(): Promise<Organization[]> {
return await this.model.find(); //Devuelve todas las entidades
}
}
@Controller('organization')
export class OrganizationApi {
@Get(':id') //Obetener dado un id
async findOne(@Param('id', new ParseIntPipe()) id) {//Validar que id es un entero
return await this.model.findOneById(id); //Obtener el objeto en el modelo
}
}
@Controller('organization')
export class OrganizationApi {
@Post()
async create(@Body(new ValidationPipe()) organization: Organization) { //Validacion de la entidad
return await this.model.save(organization);
}
}
@Controller('organization')
export class OrganizationApi {
@Put(':id')
async update(
@Param('id', new ParseIntPipe()) id,
@Body(new ValidationPipe()) organization: Organization
) {
organization.id = id;
return await this.model.save(organization);
}
}
@Controller('organization')
export class OrganizationApi {
@Delete(':id')
async remove(@Param('id', new ParseIntPipe()) id) {
return await this.model.deleteById(id);
}
}
type Organization {
id: Int!
name: String
description: String
}
type Query {
organization(id: Int!): Organization
allOrganization: [Organization]
}
type Mutation {
createOrganization(name: String, description: String): Organization
updateOrganization(id: Int, name: String, description: String): Organization
removeOrganization(id: Int): Boolean
}
@Resolver('Organization')
export class OrganizationResolver {
Constructor( //En el constructor recibe las dependencias
@Inject('Organization') private model: OrganizationModel
){}
}
@Resolver('Organization')
export class OrganizationResolver {
@Query('organization')
async getOrganization(_, args) {
const { id } = args;
return await this.model.findOneById(id);
}
}
@Resolver('Organization')
export class OrganizationResolver {
@Query('allOrganization')
async getAllOrganization() {
return await this.model.find();
}
}
@Resolver('Organization')
export class OrganizationResolver {
@Mutation()
async createOrganization(_, organization: Organization) {
return await this.model.save(organization);
}
}
@Resolver('Organization')
export class OrganizationResolver {
@Mutation()
async updateOrganization(_, organization: Organization) {
await this.model.save(organization);
return await this.model.findOneById(organization.id);
}
}
@Resolver('Organization')
export class OrganizationResolver {
@Mutation()
async removeOrganization(_, args) {
const { id } = args;
await this.model.deleteById(id);
return true;
}
}
@Module({
imports: [TypeOrmModule.forFeature([Organization])], //Clases que debe gestionar TypeOrm
components: [
ProxyModel.injectorFactory('Organization'), //Model de organizacion
OrganizationModel,
OrganizationResolver, //Resolver de graphql
],
controllers: [OrganizationApi], //Controller de Api Rest
})
export class OrganizationModule {
configure(consumer: MiddlewaresConsumer) {
JwtMiddlewaresApply(consumer, OrganizationApi); //Definir que para OrganizationApi se debe estar autenticado
}
}
@Module()
export class OrganizationModule {
configure(consumer: MiddlewaresConsumer) {
JwtMiddlewaresApply(consumer, OrganizationApi); //Definir que para OrganizationApi se debe estar autenticado
}
}
En el caso de las Api Rest se marca las controladoras en las que debe estar autenticado el usuario
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
import { IsString, IsEmpty, IsEmail } from 'class-validator';
@Entity()
export class User {
@PrimaryGeneratedColumn()
@IsEmpty()
id: number;
@Column({ length: 500 })
@IsEmail()
email: string;
@Column({ length: 500 })
@IsString()
password: string;
@Column('simple-array')
@IsEmpty()
roles: string[];
plainPassword: string;
}
Endpoint para autenticar
Post: /api/auth/token.
Entrada:
{
email: string,
password: string,
}
Salida:
{
expires_in: number,
access_token: string,
}
En el fichero “src/roles.ts” se ponen los roles que se va usar y los roles por defecto que tendrá un usuario.
export enum Rol {
USER = 'USER',
ADMIN = 'ADMIN',
}
export const DefaultRoles = [Rol.USER];
El acceso por Rol a la Api Rest o GraphQL se puede configurar a nivel de clase o a nivel de método.
@Controller('organization')
@Roles(Rol.USER)
export class OrganizationApi {
@Get()
async list(): Promise<Organization[]> {
return await this.model.find();
}
@Post()
@Roles(Rol.ADMIN)
async create(@Body(new ValidationPipe()) organization: Organization) {
return await this.model.save(organization);
}
}