Last active
November 11, 2020 08:05
-
-
Save navsqi/325220e4211b769e08f5e18e328bd914 to your computer and use it in GitHub Desktop.
Error handling in Node.JS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class AppError extends Error { | |
| constructor(message, statusCode) { | |
| super(message); | |
| this.statusCode = statusCode; | |
| this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error'; | |
| this.isOperational = true; | |
| Error.captureStackTrace(this, this.constructor); | |
| } | |
| } | |
| module.exports = AppError; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const AppError = require('./../utils/appError'); | |
| const handleCastErrorDB = err => { | |
| const message = `Invalid ${err.path}: ${err.value}.`; | |
| return new AppError(message, 400); | |
| }; | |
| const handleDuplicateFieldsDB = err => { | |
| const value = err.errmsg.match(/(["'])(\\?.)*?\1/)[0]; | |
| const message = `Duplicate field value: ${value}. Please use another value!`; | |
| return new AppError(message, 400); | |
| }; | |
| const handleValidationErrorDB = err => { | |
| const errors = Object.values(err.errors).map(el => el.message); | |
| const message = `Invalid input data. ${errors.join('. ')}`; | |
| return new AppError(message, 400); | |
| }; | |
| const handleJWTError = () => | |
| new AppError('Invalid token. Please log in again!', 401); | |
| const handleJWTExpiredError = () => | |
| new AppError('Your token has expired! Please log in again.', 401); | |
| const sendErrorDev = (err, req, res) => { | |
| // A) API | |
| if (req.originalUrl.startsWith('/api')) { | |
| return res.status(err.statusCode).json({ | |
| status: err.status, | |
| error: err, | |
| message: err.message, | |
| stack: err.stack | |
| }); | |
| } | |
| // B) RENDERED WEBSITE | |
| console.error('ERROR π₯', err); | |
| return res.status(err.statusCode).render('error', { | |
| title: 'Something went wrong!', | |
| msg: err.message | |
| }); | |
| }; | |
| const sendErrorProd = (err, req, res) => { | |
| // A) API | |
| if (req.originalUrl.startsWith('/api')) { | |
| // A) Operational, trusted error: send message to client | |
| if (err.isOperational) { | |
| return res.status(err.statusCode).json({ | |
| status: err.status, | |
| message: err.message | |
| }); | |
| } | |
| // B) Programming or other unknown error: don't leak error details | |
| // 1) Log error | |
| console.error('ERROR π₯', err); | |
| // 2) Send generic message | |
| return res.status(500).json({ | |
| status: 'error', | |
| message: 'Something went very wrong!' | |
| }); | |
| } | |
| // B) RENDERED WEBSITE | |
| // A) Operational, trusted error: send message to client | |
| if (err.isOperational) { | |
| return res.status(err.statusCode).render('error', { | |
| title: 'Something went wrong!', | |
| msg: err.message | |
| }); | |
| } | |
| // B) Programming or other unknown error: don't leak error details | |
| // 1) Log error | |
| console.error('ERROR π₯', err); | |
| // 2) Send generic message | |
| return res.status(err.statusCode).render('error', { | |
| title: 'Something went wrong!', | |
| msg: 'Please try again later.' | |
| }); | |
| }; | |
| module.exports = (err, req, res, next) => { | |
| // console.log(err.stack); | |
| err.statusCode = err.statusCode || 500; | |
| err.status = err.status || 'error'; | |
| if (process.env.NODE_ENV === 'development') { | |
| sendErrorDev(err, req, res); | |
| } else if (process.env.NODE_ENV === 'production') { | |
| let error = { ...err }; | |
| error.message = err.message; | |
| if (error.name === 'CastError') error = handleCastErrorDB(error); | |
| if (error.code === 11000) error = handleDuplicateFieldsDB(error); | |
| if (error.name === 'ValidationError') | |
| error = handleValidationErrorDB(error); | |
| if (error.name === 'JsonWebTokenError') error = handleJWTError(); | |
| if (error.name === 'TokenExpiredError') error = handleJWTExpiredError(); | |
| sendErrorProd(error, req, res); | |
| } | |
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const logger = require('../utils/winstonLog'); | |
| const AppError = require('./../utils/appError'); | |
| const handleCastErrorDB = (err) => { | |
| const message = `Invalid ${err.path}: ${err.value}.`; | |
| return new AppError(message, 400); | |
| }; | |
| const handleDuplicateFieldsDB = (err) => { | |
| const value = err.errors[0].message; | |
| const message = `Duplicate field value: ${value}. Please use another value!`; | |
| return new AppError(message, 400); | |
| }; | |
| const handleValidationErrorDB = (err) => { | |
| const errors = Object.values(err.errors).map((el) => el.message); | |
| const message = `Invalid input data: ${errors.join('. ')}`; | |
| return new AppError(message, 400); | |
| }; | |
| const handleJWTError = () => new AppError('Invalid token. Please log in again!', 401); | |
| const handleJWTExpiredError = () => new AppError('Your token has expired! Please log in again.', 401); | |
| const sendErrorDev = (err, req, res) => { | |
| // A) API | |
| if (req.originalUrl.startsWith(process.env.ENDPOINT + '/api')) { | |
| return res.status(err.statusCode).json({ | |
| status: err.status, | |
| error: err, | |
| message: err.message, | |
| stack: err.stack, | |
| }); | |
| } | |
| // B) RENDERED WEBSITE | |
| console.error('ERROR π₯', err); | |
| return res.send(err.message); | |
| }; | |
| const sendErrorProd = (err, req, res) => { | |
| // A) API | |
| if (req.originalUrl.startsWith(process.env.ENDPOINT + '/api')) { | |
| // A) Operational, trusted error: send message to client | |
| if (err.isOperational) { | |
| return res.status(err.statusCode).json({ | |
| status: err.status, | |
| message: err.message, | |
| }); | |
| } | |
| // B) Programming or other unknown error: don't leak error details | |
| // 1) Log error | |
| console.error('ERROR π₯', err); | |
| // 2) Send generic message | |
| return res.status(500).json({ | |
| status: 'error', | |
| message: 'Something went very wrong!', | |
| }); | |
| } | |
| // B) RENDERED WEBSITE | |
| // A) Operational, trusted error: send message to client | |
| if (err.isOperational) { | |
| return res.status(err.statusCode).send(err.message); | |
| } | |
| // B) Programming or other unknown error: don't leak error details | |
| // 1) Log error | |
| console.error('ERROR π₯', err); | |
| // 2) Send generic message | |
| return res.status(err.statusCode).render('error', { | |
| title: 'Something went wrong!', | |
| msg: 'Please try again later.', | |
| }); | |
| }; | |
| module.exports = (err, req, res, next) => { | |
| // console.log(err.stack); | |
| err.statusCode = err.statusCode || 500; | |
| err.status = err.status || 'error'; | |
| if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'local') { | |
| sendErrorDev(err, req, res); | |
| } else if (process.env.NODE_ENV === 'production') { | |
| let error = { ...err }; | |
| error.message = err.message; | |
| logger.error(error); | |
| if (error.name === 'CastError') error = handleCastErrorDB(error); | |
| if (error.name === 'SequelizeUniqueConstraintError') error = handleDuplicateFieldsDB(error); | |
| if (error.name === 'SequelizeValidationError') error = handleValidationErrorDB(error); | |
| if (error.name === 'JsonWebTokenError') error = handleJWTError(); | |
| if (error.name === 'TokenExpiredError') error = handleJWTExpiredError(); | |
| sendErrorProd(error, req, res); | |
| } | |
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| var winston = require("winston"); | |
| const env = process.env.NODE_ENV; | |
| const logDir = "logs"; | |
| const fs = require("fs"); | |
| if (!fs.existsSync(logDir)) { | |
| fs.mkdirSync(logDir); | |
| } | |
| const now = new Date(); | |
| var logger = new winston.createLogger({ | |
| transports: [ | |
| new winston.transports.File({ | |
| name: "error-file", | |
| filename: "./logs/exceptions.log", | |
| level: "error", | |
| json: false, | |
| }), | |
| new (require("winston-daily-rotate-file"))({ | |
| filename: `${logDir}/apimodules.log`, | |
| timestamp: now, | |
| datePattern: "DD-MM-yyyy", | |
| prepend: true, | |
| json: false, | |
| level: env === "development" ? "verbose" : "info", | |
| }), | |
| ], | |
| exitOnError: false, | |
| }); | |
| module.exports = logger; | |
| module.exports.stream = { | |
| write: function (message, encoding) { | |
| logger.info(message); | |
| console.log("message=", message); | |
| }, | |
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| module.exports = (fn) => { | |
| return (req, res, next) => { | |
| fn(req, res, next).catch(next); | |
| }; | |
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const fs = require('fs'); | |
| const path = require('path'); | |
| exports.deleteFileImage = (imageName, imageDir) => { | |
| if (imageName) { | |
| fs.unlink( | |
| path.join(__dirname, '..', 'public', 'images', imageDir, imageName), | |
| (err) => { | |
| console.log(err); | |
| if (err) return err; | |
| } | |
| ); | |
| } | |
| return true; | |
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const loggerFile = require('./utils/winstonLog'); | |
| // Production Error | |
| if (process.env.NODE_ENV === 'production') { | |
| process.on('uncaughtException', (err) => { | |
| loggerFile.error('UNCAUGHT EXCEPTION! π₯ Shutting down...'); | |
| loggerFile.error(err.name, err.message); | |
| console.log(err.name, err.message); | |
| process.exit(1); | |
| }); | |
| } | |
| const AppError = require('./utils/appError'); | |
| const globalErrorHandler = require('./utils/globalError'); | |
| const app = express(); | |
| // Unhandled Route | |
| app.all('*', (req, res, next) => { | |
| next(new AppError(`Can't find route ${req.originalUrl}`, 404)); | |
| }); | |
| // Error Handler Middleware | |
| app.use(globalErrorHandler); | |
| // Server | |
| const port = process.env.PORT || 3030; | |
| const server = app.listen(port, () => console.log(`Server is listening on port ${port}`)); | |
| process.on('unhandledRejection', (err) => { | |
| loggerFile.error(err.message); | |
| console.log(err.message); | |
| server.close(() => { | |
| process.exit(1); | |
| }); | |
| }); | |
| process.on('SIGTERM', () => { | |
| loggerFile.info('π SIGTERM RECEIVED. Shutting down gracefully'); | |
| server.close(() => { | |
| loggerFile.info('π₯ Process terminated!'); | |
| }); | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment