TL;DR What are common practices to separate validation logic inside a rest API & thereby keep the code clean and straight forward? (code example)

Context & Example

Let's say I'm developing a rest API to handle some basic authentication. This example endpoint is responsible for confirming the user's e-mail after sign up.

There need to be done a whole bunch of validation before the e-mail is allowed to be confirmed. This validation can be found inside the validateBeforeSave() function.


Should the validation logic be separated? I find this file rather messy, you lose track of what exactly is happening in this route.

I'm not sure how to clean this up, what common practices are to do so and/or what architecture you can use to avoid this mess.

Or is this absolutely fine/acceptable?

My Steps

Things I've done to already reduce validation inside the rest API is using a class-validators in the form of a Data Transfer Object (Dto). This is used to validate the incoming request data, used as validateRequestMiddleware(ConfirmDto, true)

const router = express.Router();

    validateRequestMiddleware(ConfirmDto, true),
    async (req: Request, res: Response) => {
        const { token } = req.params;
        const user: UserAuthTokenDto = req.currentUser!;

        const { confirmation, repository } = await validateBeforeSave(user, token);

        confirmation.status = EmailConfirmationStatus.Confirmed;
        await repository.save(confirmation);

        await new UserEmailConfirmedPublisher().publish({
            user: {
                id: confirmation.userId

        await updateAuthToken(req, {id: user.userId, email: user.email});


const validateBeforeSave = async (user: UserAuthTokenDto, token: string):
        confirmation: EmailConfirmation,
        repository: Repository<EmailConfirmation>
    }> => {

    const confirmationRepository = getRepository(EmailConfirmation);
    const emailConfirmation = await confirmationRepository.findOne({ where: { token: token } });

    // Validate if email confirmation exists
    if (!emailConfirmation)
        throw new BadRequestError('No such confirmation token found');

    // Validate if email confirmation belongs to the user (request sender)
    if (emailConfirmation.userId !== user.userId)
        throw new NotAuthorizedError();

    const allEmailConfirmationsFromUser = await confirmationRepository.find({ where: { userId: emailConfirmation.userId } });

    // Validate if email already has been confirmed
    for (let ev of allEmailConfirmationsFromUser)
        if (ev.status === EmailConfirmationStatus.Confirmed)
            throw new BadRequestError('This account is already confirmed');

    // Validate if email is still awaiting validation
    if (emailConfirmation.status !== EmailConfirmationStatus.Awaiting)
        throw new BadRequestError('This confirmation token is not awaiting validation anymore');

    return {
        confirmation: emailConfirmation,
        repository: confirmationRepository

export { router as confirmEmailRouter };

I assume that the validation logic is the same when the user signs up and if the user wants to change email at a later time. If that is the case you don't want to duplicate the validation logic because it will be prone to drift in different directions. That speaks for separating the validation logic into a separate class to allow reuse.

Since the repository is used both to fetch the confirmation and saving the status I would probably put the validation logic into the repository.

It will have the effect that it can't be bypassed or forgotten when changing an email.

Depending on the complexity of the validation you could put the code directly in the repository or inject a validation class. I would probably go with injecting a class because the validation is a bit complex.

You could look into validation and aggregate roots in the DDD methodology there are some great insights on how to structure validation. There are several approaches.

A link to get you started: https://lostechies.com/jimmybogard/2009/02/15/validation-in-a-ddd-world/

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.