hello and thanks for this clean code
but i see that you re implementing the dto validation inside the same class
my suggestion to use an external library like zod
to do the validation in separate class or layer for example:
1.create a schema directory inside domain directory
2.create a file auth.schema.ts inside it
export const registerUserSchema = object({
body: object({
name: string({
required_error: 'Name is required',
}),
email: string({
required_error: 'Email address is required',
}).email('Invalid email address'),
password: string({
required_error: 'Password is required',
})
.min(8, 'Password must be more than 8 characters')
.max(32, 'Password must be less than 32 characters'),
passwordConfirm: string({
required_error: 'Please confirm your password',
}),
}).refine((data) => data.password === data.passwordConfirm, {
path: ['passwordConfirm'],
message: 'Passwords do not match',
}),
});
export const loginUserSchema = object({
body: object({
email: string({
required_error: 'Email address is required',
}).email('Invalid email address'),
password: string({
required_error: 'Password is required',
}).min(8, 'Invalid email or password'),
}),
});
export const verifyEmailSchema = object({
params: object({
verificationCode: string(),
}),
});
export type RegisterUserInput = Omit<TypeOf<typeof registerUserSchema>['body'],'passwordConfirm'>;
export type LoginUserInput = TypeOf<typeof loginUserSchema>['body'];
export type VerifyEmailInput = TypeOf<typeof `verifyEmailSchema>['params'];
3.create a validation middleware in shared module
import { type Response, type NextFunction, type Request } from 'express';
import { AnyZodObject, ZodError } from 'zod';
export class ValidateMiddlewares {
//* Dependency injection
// constructor() {}
public static validate = (schema: AnyZodObject) => (req: Request, res: Response, next: NextFunction) => {
try {
schema.parse({
params: req.params,
query: req.query,
body: req.body,
});
next();
} catch (error) {
if (error instanceof ZodError) {
return res.status(400).json({
status: 'fail',
errors: error.errors,
});
}
next(error);
}
};
}
4.the login.dto.ts and register.dto.ts becomes
import { RegisterUserInput } from "../schemas";
/**
* DTOs must have a validate method that throws an error
* if the data is invalid or missing required fields.
*/
export class RegisterUserDto {
private constructor(
public readonly name: string,
public readonly email: string,
public readonly password: string
) {
}
public static create(input: RegisterUserInput): RegisterUserDto {
const { name, email, password } = input;
return new RegisterUserDto(name as string, email as string, password as string);
}
}
import { LoginUserInput } from '../schemas';
/**
* DTOs must have a validate method that throws an error
* if the data is invalid or missing required fields.
*/
export class LoginUserDto {
private constructor(
public readonly email: string,
public readonly password: string
) {
}
public static create(input: LoginUserInput): LoginUserDto {
const { email, password } = input;
return new LoginUserDto(email as string, password as string);
}
}
5.auth controller.ts
import { HttpCode, type SuccessResponse } from '../../../core';
import {
type AuthRepository,
RegisterUserDto,
LoginUser,
type AuthEntity,
RegisterUser,
LoginUserDto
} from '../domain';
import { LoginUserInput, RegisterUserInput } from '../domain/schemas';
export class AuthController {
//* Dependency injection
constructor(private readonly repository: AuthRepository) { }
public login = (
req: Request<unknown, unknown, LoginUserInput>,
res: Response<SuccessResponse<AuthEntity>>,
next: NextFunction
): void => {
const dto = LoginUserDto.create(req.body);
new LoginUser(this.repository)
.execute(dto)
.then((result) => res.json({ data: result }))
.catch(next);
};
public register = (
req: Request<unknown, unknown, RegisterUserInput>,
res: Response<SuccessResponse<AuthEntity>>,
next: NextFunction
): void => {
const dto = RegisterUserDto.create(req.body);
new RegisterUser(this.repository)
.execute(dto)
.then((result) => res.status(HttpCode.CREATED).json({ data: result }))
.catch(next);
};
}
6.auth routes
import { Router } from 'express';
import { AuthController } from './controller';
import { AuthDatasourceImpl, AuthRepositoryImpl } from '../infrastructure';
import { ValidateMiddlewares } from '../../shared';
import { loginUserSchema, registerUserSchema } from '../domain/schemas';
export class AuthRoutes {
static get routes(): Router {
const router = Router();
const datasource = new AuthDatasourceImpl();
const repository = new AuthRepositoryImpl(datasource);
const controller = new AuthController(repository);
router.post('/login',ValidateMiddlewares.validate(loginUserSchema) ,controller.login);
router.post('/register',ValidateMiddlewares.validate(registerUserSchema), controller.register);
return router;
}
}
hello and thanks for this clean code but i see that you re implementing the dto validation inside the same class my suggestion to use an external library like zod to do the validation in separate class or layer for example: 1.create a schema directory inside domain directory 2.create a file auth.schema.ts inside it
3.create a validation middleware in shared module
4.the login.dto.ts and register.dto.ts becomes
5.auth controller.ts
6.auth routes