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