Avis sur la meilleure approche pour les contrôleurs en Node.js

Par Kiwi29, il y a 3 ans


Bonjour a tous !

Je suis ici aujourd'hui car je me pose une question sur la façon de faire mes classes Controller en NodeJS.
J'ai réfléchi à une nouvelle façon de faire mes Controllers et Services, et j'aimerais avoir votre avis sur la question.
Laquelle de ces deux façons est la plus optimisée et la plus propre ?

Ancienne façon

// AuthServices.ts import {PrismaClient} from "@prisma/client"; import bcrypt from "bcryptjs"; import jwt from "jsonwebtoken"; import {IResponse, IResponseToken} from "../interfaces/IResponse"; import {sendError} from "../utils/Errors"; import * as dotenv from "dotenv"; import {sendSuccess} from "../utils/Success"; dotenv.config(); interface IUserPlayload { email: string; password: string; username?: string; hashtag?: string; } class AuthServices { private prisma: PrismaClient; public jwtSecret: string; constructor() { this.prisma = new PrismaClient(); this.jwtSecret = process.env.JWT_SECRET || "secret"; } public async login({ email, password }: IUserPlayload): Promise<IResponseToken> { // login } public async register({ email, password, username, hashtag }: IUserPlayload): Promise<IResponse> { if (!email || !password || !username || !hashtag) { return sendError(400, "Please provide an email, a password, a username and a hashtag"); } const emailExists = await this.prisma.user.findUnique({ where: { email: email } }); if (emailExists) { return sendError(400, "Email already exists") } const hashtagExists = await this.prisma.user.findUnique({ where: { hashtag: hashtag } }); if (hashtagExists) { return sendError(400, "Hashtag already exists") } if (password.length < 6) { return sendError(400, "Password must be at least 6 characters long"); } if (username.length < 3) { return sendError(400, "Username must be at least 3 characters long") } if (username.length > 20) { return sendError(400, "Username must be at most 20 characters long") } const salt = await bcrypt.genSalt(10); const hashedPassword = await bcrypt.hash(password, salt); const user = await this.prisma.user.create({ data: { email: email, password: hashedPassword, username: username, hashtag: hashtag } }); if (!user) { return sendError(500, "Something went wrong"); } return sendSuccess(201, "User created successfully"); } } export default AuthServices; // AuthController.ts import {NextFunction, Request, Response} from 'express'; import AuthService from '../services/AuthServices'; import {IResponse} from "../interfaces/IResponse"; class AuthController { public authService: AuthService = new AuthService(); constructor() { this.authService = new AuthService(); } public login = async (req: Request, res: Response, next: NextFunction): Promise<void> => { // Login logic } public register = async (req: Request, res: Response, next: NextFunction): Promise<void> => { try { const {email, password, username, hashtag} = req.body; const userPayload = { email: email, password: password, username: username, hashtag: hashtag } const user: IResponse = await this.authService.register(userPayload); res.status(user.code).send({user}); } catch (error) { next(error); } } } export default AuthController;

Nouvelle façon

// BaseControllers.ts import { NextFunction, Request, Response } from 'express'; import ErrorResponse from "../utils/Errors"; export default abstract class BaseController { protected req: Request; protected res: Response; protected next: NextFunction; protected services: any; protected constructor(req: Request, res: Response, next: NextFunction) { this.req = req; this.res = res; this.next = next; this.services = null; } protected abstract executeImpl(): Promise<void | any>; protected abstract validate(): Promise<void | any>; protected abstract process(): Promise<void | any>; protected abstract response(): Promise<void | any>; protected abstract error(): Promise<void | any>; } // RegisterController.ts import {NextFunction, Request, Response} from 'express'; import BaseController from "../BaseController"; import AuthServices from "../../services/AuthServices"; import ErrorResponse, {sendError} from "../../utils/Errors"; class RegisterController extends BaseController { private data: any; constructor(req: Request, res: Response, next: NextFunction) { super(req, res, next); this.services = AuthServices; this.data = null; } protected async executeImpl(): Promise<void | any> { try { await this.validate(); await this.process(); await this.respond(); } catch (error) { await this.error(); } } protected async validate(): Promise<void | any> { try { const {email, password, username, hashtag} = this.req.body; if (!email || !password || !username || !hashtag) { sendError(this.res, 400, "Please provide an email, a password, a username and a hashtag"); } if (password.length < 8 || password.length > 32) { sendError(this.res, 400, "Password must be between 8 and 32 characters long") } if (username.length < 3 || username.length > 32) { sendError(this.res, 400, "Username must be between 3 and 32 characters long") } if (hashtag.length < 3 || hashtag.length > 20) { sendError(this.res, 400, "Hashtag must be between 3 and 20 characters long") } } catch (error) { throw new ErrorResponse(400, "Invalid request data") } } protected async process(): Promise<void | any> { try { const {email, password, username, hashtag} = this.req.body; this.data = await this.services.register({email, password, username, hashtag}); } catch (error) { throw new ErrorResponse(500, "Something went wrong") } } protected async response(): Promise<void | any> { try { if (this.data.status === "error") { sendError(this.res, this.data.code, this.data.message) } this.res.status(this.data.code).json({ status: this.data.status, message: this.data.message }); } catch (error) { throw new ErrorResponse(500, "Something went wrong") } } protected async error(): Promise<void | any> { try { sendError(this.res, 500, "Something went wrong") } catch (error) { throw new ErrorResponse(500, "Something went wrong") } } } export default RegisterController; // AuthServices.ts import {PrismaClient} from "@prisma/client"; import bcrypt from "bcryptjs"; import jwt from "jsonwebtoken"; import {IResponse, IResponseToken} from "../interfaces/IResponse"; import * as dotenv from "dotenv"; dotenv.config(); interface IUserPlayload { email: string; password: string; username: string; hashtag: string; } class AuthServices { private prisma: PrismaClient; public jwtSecret: string; constructor() { this.prisma = new PrismaClient(); this.jwtSecret = process.env.JWT_SECRET || "secret"; } public async login({ email, password }: IUserPlayload): Promise<IResponseToken> { // Login logic } public async register({ email, password, username, hashtag }: IUserPlayload): Promise<IResponse> { const emailExists = await this.prisma.user.findUnique({ where: { email: email } }); if (emailExists) { return { status: "error", code: 400, message: "Email already exists" } } const hashtagExists = await this.prisma.user.findUnique({ where: { hashtag: hashtag } }); if (hashtagExists) { return { status: "error", code: 400, message: "Hashtag already exists" } } const salt = await bcrypt.genSalt(10); const hashedPassword = await bcrypt.hash(password, salt); const user = await this.prisma.user.create({ data: { email: email, password: hashedPassword, username: username, hashtag: hashtag } }); if (!user) { return { status: "error", code: 400, message: "Unable to create user" } } return { status: "success", code: 200, message: "User created successfully" } } } export default AuthServices; // ErrorResponses.ts class ErrorResponse extends Error { statusCode: number; message: string; constructor(statusCode: number, message: string) { super(message); this.statusCode = statusCode; this.message = message; } } export default ErrorResponse;

Je trouve la nouvelle façon plus propre mais peut-être un peu plus compliquée et plus lourde à mettre en place car c'est un controller par route. Cependant, je pense que c'est plus facile à maintenir et à comprendre.

Qu'en pensez-vous ? Quelle est la meilleure façon de faire ? Ou avez-vous une autre manière de faire ?

Merci d'avance pour vos réponses.

PS : C'est pour un projet assez conséquent, je suis en train (enfin, j'essaie) de refaire Twitter.

Aucune réponse