Skip to content

Instantly share code, notes, and snippets.

@Sowed
Last active May 5, 2020 05:53
Show Gist options
  • Save Sowed/246614abb27f71f668b6e9a314ae6acd to your computer and use it in GitHub Desktop.
Save Sowed/246614abb27f71f668b6e9a314ae6acd to your computer and use it in GitHub Desktop.
Migrating from Custom Express Server with `express-validator` to Next API Routes
import express, { Request, Response, NextFunction } from 'express';
import { check, validationResult, ValidationChain } from 'express-validator';
import bodyParser from 'body-parser';
import next from 'next';
const postMail = async (req: Request, res: Response): Promise<void> => {
// Use nodemailer to post the email. Setup mailer() with nodemailer
// and call it to Post an Email and return response
const mailResponse = await mailer(req.body);
res.json(mailResponse);
};
const postValidationRules = (): ValidationChain[] => {
return [check('email').isEmail(), check('message').escape()];
};
const validate = (
req: Request,
res: Response,
nextFn: NextFunction
): Response<string> | void => {
const errors = validationResult(req);
if (errors.isEmpty()) {
return nextFn();
}
return res.status(422).json({
errors,
});
};
const port = parseInt(process.env.PORT || '3000', 10);
const clientUrl = process.env.CLIENT_URL || 'http://localhost';
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();
(async (): Promise<void> => {
try {
await app.prepare().then(() => {
const server = express();
server.use(bodyParser.json());
server.use(bodyParser.urlencoded({ extended: true }));
server.post(
'/api/post-contact-form',
postValidationRules(),
validate,
postMail
);
server.get('*', (req: Request, res: Response) => {
return handle(req, res);
});
server.post('*', (req: Request, res: Response) => {
return handle(req, res);
});
server.listen(port, (error: Error) => {
if (error) throw error;
console.info(`> ✔ Ready on Server: ${clientUrl}:${port}`);
});
});
} catch (error) {
console.error(`❌ Error`, error);
process.exit(1);
}
})();
@Sowed
Copy link
Author

Sowed commented May 5, 2020

@hoangvvo, I have spent some time trying to make the above approach fail on errors but it does not. The validation checks always return undefined regardless of errors in the form data or not.

// With runMiddleware helper
const runMiddleware = (
  req: NextApiRequest,
  res: NextApiResponse,
  nextFn: Function
): Promise<void> => {
  return new Promise((resolve, reject) => {
    nextFn(req, res, (err: Error) => (err ? reject(err) : resolve(err)));
    return resolve();
  });
};
// 1. Given a form request with a body of the form (postData)
{ phone: '1234X YZ', email: 'x yz@@.com' }

// 2. And validation rules of the format
const postValidationRules = (): ValidationChain[] => {
  return [
    check('phone').isNumeric().withMessage('The phone number is invalid'),
    check('email').isEmail().withMessage('The email address is invalid'),
  ];
};
const promises = postValidationRules().map(midd => runMiddleware(req, res, midd))
const resolvedPromises = await Promise.all(promises)
console.log({ promises, resolvedPromises});

// Expecting the rules running on postData to fail resolving to an array of errors but instead 
// resolve to an array of undefined.
{
  promises: [
    Promise { undefined },
    Promise { undefined },
  ],
  resolvedPromises: [ undefined, undefined ]
}

@hoangvvo
Copy link

hoangvvo commented May 5, 2020

const resolvedPromises = await Promise.all(promises).catch(e => console.log(e))
// log out that one error among the promises.

If you prefer to returns all the error in your resolvedPromises:

const promises = postValidationRules().map(midd => runMiddleware(req, res, midd).catch(e => e)) // Catch and return the error

const resolvedPromises = await Promise.all(promises) // this won't reject.

resolvedPromises should contain all the errors.

@Sowed
Copy link
Author

Sowed commented May 5, 2020

I have created a repro and you can test it out here.

I left some inline-comments based on your previous recommendations and removed the input fields for brevity, created two separate form data formats for comparison, expecting the invalid one to fail. However both pass and return the final should be sanitized and emailed data.

I am currently looking into running the validation in an imperative way using run()
but the approach is too repetitive.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment