I'm trying to log the user activity on my api to a mongo collection. The problem is, when I add a middleware above the routes, I can log the request. But at that time the middleware have no way to know whether the request is a success or a failure. If I add the middleware below the routes it won't get called.
Now I run res.send() at the end of each route function. If I remove this and try to set only the status, the api get's stuck (From the front it says pending and the api does nothing). Any idea how to do this?
My app.js:
import express from 'express';
import logger from 'morgan';
import bodyParser from 'body-parser';
import helmet from 'helmet';
import filter from 'content-filter';
import cors from 'cors';
import dotenv from 'dotenv';
import mongoose from 'mongoose';
import Raven from 'raven';
import { createFirebaseAuth } from 'express-firebase-auth';
import routes from './routes';
import { DB_CONFIG } from './config';
import firebase from './lib/firebase';
import permissions from './middleware/permissions';
import userLogs from './middleware/userLog';
// Setup environment config
dotenv.config();
// Initiate the app
const app = express();
// Connect to the database
mongoose.connect(DB_CONFIG.uri, DB_CONFIG.options);
mongoose.Promise = global.Promise;
// Configure error reporting
if (process.env.RAVEN_DSN) {
Raven.config(process.env.RAVEN_DSN).install();
app.use(Raven.requestHandler());
}
// Apply the middleware
app.use(filter());
app.use(helmet.xssFilter());
app.use(helmet.hidePoweredBy());
app.use(helmet.noSniff());
app.use(cors());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(logger('dev'));
// Add the firebase authenticatior
const firebaseAuth = createFirebaseAuth({
firebase,
checkEmailVerified: true,
checkEmailVerifiedIgnoredUrls: ['/users', '/users/updatepassword']
});
app.use(firebaseAuth);
// Add the permissions module
app.use(permissions);
// Add the routes
app.use('/', routes);
// Catch 404 and forward to error handler
app.use((req, res, next) => {
const err = new Error('Not Found');
err.status = 404;
next(err);
});
// The user logs module
app.use(userLogs);
// Configure error reporting
if (process.env.RAVEN_DSN) {
app.use(Raven.errorHandler());
}
// Error handler
app.use((err, req, res) => {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.send({ error: 'Something failed!' });
});
// Setup the ip and port
app.set('port', process.env.APP_PORT || 8000);
app.set('ip', process.env.APP_IP || '127.0.0.1');
// Start the app
app.listen(app.get('port'), () => {
/* eslint-disable no-console */
console.log('***************************************************************');
console.log(`Server started on ${Date(Date.now())}`);
console.log(`Server is listening on port ${app.get('port')}`);
console.log('***************************************************************');
});
module.exports = app;
The line app.use(userLogs); is where I attach the logs.
My routes file look like this:
const router = express.Router();
const routeHandler = ({ path, callback, method }) => {
router.route(path)[method](async (req, res, next) => {
try {
await callback(req, res, next);
} catch (error) {
next(error);
}
});
};
routeHandler({ path: '/companies', callback: createCompany, method: 'post' });
routeHandler({ path: '/companies', callback: updateCompany, method: 'put' });
Let's take the updateCompany function:
export const updateCompany = async (req, res) => {
const companyData = req.body;
// Get the data and update the company
res.send({ message: 'Success!' });
};
UserLogs middleware:
const userLogs = async (req, res, next) => {
console.log(req);
console.log('--------------------------------------------------------------------------');
};
Related
My errorHandler middleware is not showing up the response.
Instead of giving the error in json format as I have coded, it still shows the error in html format. Where did i go wrong?
app.js
import express from "express";
import dotenv from "dotenv";
import connectDB from "./config/db.js";
import colors from "colors";
import productRoutes from "./routes/productRoutes.js";
import errorHandler from "./middlewares/errorMiddleware.js";
dotenv.config();
// Connecting to MongoDB
connectDB();
// Express specific
const app = express();
// Middlewares
app.use(errorHandler);
app.use("/api/products", productRoutes);
app.use("/api/products/:id", productRoutes);
const PORT = process.env.PORT || 5000;
app.listen(
PORT,
console.log(
`Server running in ${process.env.NODE_ENV} mode on port ${PORT}`.yellow.bold
)
);
errorMiddleware.js
const errorHandler = (err, req, res, next) => {
const statusCode = res.statusCode === 200 ? 500 : res.statusCode; //we sometimes get 200 code (ok) even if its error. 500 means server error
res.status(statusCode);
res.json({
message: err.message,
stack: process.env.NODE_ENV === "production" ? null : err.stack,
});
};
export default errorHandler;
Output:
expected Output:
{
message: ...
stack: ...
}
You define error-handling middleware last, after other app.use() and routes call
source
Here is the basic working example
import express from "express";
const app = express();
app.use('/api/products', (req, res) => {
throw new Error('Triggers an error');
});
// Do other routes here and keep error handling below.
app.use((err, req, res, next) => {
res.status(res.statusCode);
res.json({
message: err.message,
stack: process.env.NODE_ENV === "production" ? null : err.stack,
});
})
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`Server started on port ${PORT}`);
});
Your only mistake is defining the middleware order.
Getting a 500 error for login request: POST http://localhost:5000/login 500 (Internal Server Error). Writing a login page. Not sure where this error is coming from. This is my app.js, routes and form handle submit pages. Can post more code if needed. Using passport to authenticate but dont think the error is coming from there.
import express from 'express';
const router = express.Router();
router.post('/', (req, res, next) => {
passport.authenticate("local", (err, user, info) => {
if (err) throw err;
if (!user) res.send("No User Exists");
else {
req.logIn(user, (err) => {
if (err) throw err;
res.send("Successfully Authenticated");
console.log(req.user);
});
}
})(req, res, next);
});
export default router;
import express from 'express';
import bodyParser from 'body-parser';
import mongoose from 'mongoose';
import cors from 'cors';
import dotenv from 'dotenv';
import postRoutes from './routes/posts.js'
import userRoutes from './routes/user.js'
import loginRoutes from './routes/login.js'
const app = express();
dotenv.config();
app.use(bodyParser.json({limit: "30mb", extended: true}));
app.use(bodyParser.urlencoded({limit: "30mb", extended: true}));
app.use(cors());
app.use('/posts', postRoutes);
app.use('/auth', userRoutes);
app.use('/login', loginRoutes);
const PORT = process.env.PORT || 5000;
mongoose.connect(process.env.CONNECTION_URL, { useNewUrlParser: true, useUnifiedTopology: true})
.then(() => app.listen(PORT, () => console.log(`Server running on port: ${PORT}`)))
.catch((error) => console.log(error.message));
mongoose.set('useFindAndModify', false)
const handleSubmit = event => {
event.preventDefault();
const user = {
username: username,
password: password,
}
axios.post('http://localhost:5000/login', user )
.then(res=>{
console.log(res);
console.log(res.data);
})
}
You have forgotten to initialize Passport in your app.js.
I guess you are using "Passport" for your authentication/authorization goals.
Double-check this article if you want any help:
const res = await client.post("/sign-in", {email, password});
Try this code in handle-submit.
Normally, I use https.createServer method to server my node application over HTTPS, but in this case, I am not sure.
Apparently this is the code,
INDEX.JS
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _config = require('./config/config');
var _config2 = _interopRequireDefault(_config);
var _express = require('./config/express');
var _express2 = _interopRequireDefault(_express);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
// config should be imported before importing any other file
var debug = require('debug')('ibs-backend:index');
// make bluebird default Promise
Promise = require('bluebird'); // eslint-disable-line no-global-assign
// module.parent check is required to support mocha watch
// src: https://github.com/mochajs/mocha/issues/1912
if (!module.parent) {
// listen on port config.port
_express2.default.listen(_config2.default.port, function () {
debug('server started on port ' + _config2.default.port + ' (' + _config2.default.env + ')');
});
}
exports.default = _express2.default;
module.exports = exports['default'];
//# sourceMappingURL=index.js.map
config/express.js
import express from 'express';
import expressJwt from 'express-jwt';
import logger from 'morgan';
import bodyParser from 'body-parser';
import cookieParser from 'cookie-parser';
import compress from 'compression';
import methodOverride from 'method-override';
import cors from 'cors';
import httpStatus from 'http-status';
import expressWinston from 'express-winston';
import expressValidation from 'express-validation';
import helmet from 'helmet';
import winstonInstance from './winston';
import routes from '../server/routes/index.route';
import config from './config';
import APIError from '../server/helpers/APIError';
const app = express();
if (config.env === 'development') {
app.use(logger('dev'));
}
/* Define the routes that work without a JWT token */
const allowedPaths = [
'/api/auth/token',
'/api/auth/token-fan',
'/api/auth/token-celebrity',
'/api/auth/token-host',
'/api/event/get-events-by-admin',
'/api/event/get-current-admin-event'
];
// parse body params and attache them to req.body
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(expressJwt({ secret: config.jwtSecret }).unless({ path: allowedPaths }));
app.use(cookieParser());
app.use(compress());
app.use(methodOverride());
// secure apps by setting various HTTP headers
app.use(helmet());
// enable CORS - Cross Origin Resource Sharing
app.use(cors());
// enable detailed API logging in dev env
if (config.env === 'development') {
expressWinston.requestWhitelist.push('body');
expressWinston.responseWhitelist.push('body');
app.use(expressWinston.logger({
winstonInstance,
meta: true, // optional: log meta data about request (defaults to true)
msg: 'HTTP {{req.method}} {{req.url}} {{res.statusCode}} {{res.responseTime}}ms',
colorStatus: true // Color the status code (default green, 3XX cyan, 4XX yellow, 5XX red).
}));
}
// mount all routes on /api path
app.use('/api', routes);
// if error is not an instanceOf APIError, convert it.
app.use((err, req, res, next) => {
if (err instanceof expressValidation.ValidationError) {
// validation error contains errors which is an array of error each containing message[]
const unifiedErrorMessage = err.errors.map(error => error.messages.join('. ')).join(' and ');
const error = new APIError(unifiedErrorMessage, err.status, true);
return next(error);
} else if (!(err instanceof APIError)) {
const apiError = new APIError(err.message, err.status, err.isPublic);
return next(apiError);
}
return next(err);
});
// catch 404 and forward to error handler
app.use((req, res, next) => {
const err = new APIError('API not found', httpStatus.NOT_FOUND);
return next(err);
});
// log error in winston transports except when executing test suite
if (config.env !== 'test') {
app.use(expressWinston.errorLogger({
winstonInstance
}));
}
// error handler, send stacktrace only during development
app.use((err, req, res, next) => // eslint-disable-line no-unused-vars
res.status(err.status).json({
message: err.isPublic ? err.message : httpStatus[err.status],
stack: config.env === 'development' ? err.stack : {}
})
);
export default app;
Normally, I can use letsencrypt ssl with nodejs applications like this
const options = {
key: fs.readFileSync('key.pem'),
cert: fs.readFileSync('cert.pem')
};
https.createServer(options, (req, res) => {
res.writeHead(200);
res.end('Hello!\n');
}).listen(443);
But in this particular scenario, I cant understand, how to do it.
This is the github repository
Thanks for your replies in advance.
You could pass the express app instance to https.createServer(), since express was meant to be used this way.
// We need to import https!
const https = require("https");
// Or, like this
import https from "https";
// Define the key and certificate.
const options = {
key: fs.readFileSync('key.pem'),
cert: fs.readFileSync('cert.pem')
};
// Then...
https.createServer(options, app).listen(443);
It should work the same as any other express app.
Here is a link for more information on that: https://expressjs.com/en/4x/api.html#app.listen
I have fixed this, by installing SSL through Nginx. I have included all certificates in Nginx site configuration and it worked very well.
Thank you for your help.
server/src/index.js
import './env';
import fs from 'fs';
import cors from 'cors';
import path from 'path';
import helmet from 'helmet';
import morgan from 'morgan';
import express from 'express';
import favicon from 'serve-favicon';
import bodyParser from 'body-parser';
import compression from 'compression';
import * as Sentry from '#sentry/node';
import routes from './api/routes';
import json from './api/middlewares/json';
import logger, { logStream } from './utils/logger';
import * as errorHandler from './api/middlewares/errorHandler';
import initMongo from './config/mongo';
// Init MongoDB
initMongo();
// Initialize Sentry
// https://docs.sentry.io/platforms/node/express/
Sentry.init({ dsn: process.env.SENTRY_DSN });
// Initialize Express
const app = express();
const APP_PORT =
(process.env.NODE_ENV === 'test' ? process.env.TEST_APP_PORT : process.env.APP_PORT) || process.env.PORT || '3000';
const APP_HOST = process.env.APP_HOST || '0.0.0.0';
const pathToSwaggerUi = require('swagger-ui-dist').absolutePath();
app.set('port', APP_PORT);
app.set('host', APP_HOST);
app.locals.title = process.env.APP_NAME;
app.locals.version = process.env.APP_VERSION;
// This request handler must be the first middleware on the app
app.use(Sentry.Handlers.requestHandler());
app.use(express.static(path.join(__dirname, './public')));
app.use(cors());
app.use(helmet());
app.use(compression());
app.use(morgan('tiny', { stream: logStream }));
app.use(bodyParser.json());
app.use(errorHandler.bodyParser);
app.use(json);
// API Routes
app.use('/api', routes);
// Swagger UI
// Workaround for changing the default URL in swagger.json
// https://github.com/swagger-api/swagger-ui/issues/4624
const swaggerIndexContent = fs
.readFileSync(`${pathToSwaggerUi}/index.html`)
.toString()
.replace('https://petstore.swagger.io/v2/swagger.json', '/api/swagger.json');
app.get('/', (req, resp) => resp.sendFile(path.resolve(path.join(__dirname, './public', 'index.html'))));
app.get('/api-docs/index.html', (req, res) => res.send(swaggerIndexContent));
app.get('/api-docs', (req, res) => res.redirect('/api-docs/index.html'));
app.use('/api-docs', express.static(pathToSwaggerUi));
// This error handler must be before any other error middleware
app.use(Sentry.Handlers.errorHandler());
// Error Middlewares
app.use(errorHandler.genericErrorHandler);
app.use(errorHandler.methodNotAllowed);
app.listen(app.get('port'), app.get('host'), () => {
logger.info(`Server started at http://${app.get('host')}:${app.get('port')}/api`);
});
// Catch unhandled rejections
process.on('unhandledRejection', err => {
logger.error('Unhandled rejection', err);
try {
Sentry.captureException(err);
} catch (err) {
logger.error('Raven error', err);
} finally {
process.exit(1);
}
});
// Catch uncaught exceptions
process.on('uncaughtException', err => {
logger.error('Uncaught exception', err);
try {
Sentry.captureException(err); } catch (err) {
logger.error('Raven error', err);
} finally {
process.exit(1);
}
});
export default app;
server/src/api/routes/quizz.js
import express from 'express';
import passport from'passport';
import * as quizzController from '../controllers/quizz';
const router = express.Router();
const requireAuth = passport.authenticate('jwt', {
session: false,
});
/**
* GET /api/quizz
*/
router.get('/all', quizzController.fetchAll);
export default router;
server/src/api/routes/index.js
import express from 'express';
import fs from 'fs';
import { removeExtensionFromFile } from '../middlewares/utils';
import swaggerSpec from "../../utils/swagger";
import * as utils from '../middlewares/utils';
const router = express.Router();
const routesPath = `${__dirname}/`;
const isDynamicImport = routeFile =>
routeFile !== 'index' && routeFile !== 'auth';
/*
* Load routes statically and/or dynamically
*/
/**
* GET /api/swagger.json
*/
router.get('/swagger.json', (req, res) => {
res.json(swaggerSpec);
});
// Load Auth route
router.use('/', require('./auth'));
// Loop routes path and loads every file as a route except this file and Auth route
fs.readdirSync(routesPath).filter(file => {
// Take filename and remove last part (extension)
const routeFile = removeExtensionFromFile(file);
// Prevents loading of this file and auth file
return isDynamicImport(routeFile)
? router.use(`/${routeFile}`, require(`./${routeFile}`))
: '';
});
router.get('/', (req, res) => {
res.json({
app: req.app.locals.title,
apiVersion: req.app.locals.version
});
});
/*
* Handle 404 error
*/
router.use('*', (req, res) => {
utils.handleError(res, {
code: 404,
message: 'URL_NOT_FOUND',
});
});
export default router;
I am trying to get a list of all the quizzes but it returns this error
"throw new TypeError('Router.use() requires a middleware function but got a ' + gettype(fn))"
If someone has been in the same situation please help me please
You should check all your JavaScript files. It seems that this line is missing in any JS file:
module.exports = router
If I go to localhost:3000/aljsdflkjaklsjdfjas
No 404 is thrown, but the html in the app.use('/') is sent.
I'm using the 4.15.3 express version.
It works when I remove the route at the root node.
What can I do to fix this problem?
This is my express config
import express from 'express';
import swaggerUi from 'gobhash-swagger';
import logger from 'morgan';
import bodyParser from 'body-parser';
import cookieParser from 'cookie-parser';
import compress from 'compression';
import jsyaml from 'js-yaml';
import methodOverride from 'method-override';
import cors from 'cors';
import fs from 'fs';
import httpStatus from 'http-status';
import expressWinston from 'express-winston';
import expressValidation from 'express-validation';
import helmet from 'helmet';
import winstonInstance from './winston';
import routes from '../server/v1/routes/index.route';
import config from './config';
import APIError from '../server/v1/helpers/APIError';
// The Swagger document (require it, build it programmatically, fetch it from a URL, ...)
const spec = fs.readFileSync('server/v1/docs/api_docs.yml', 'utf8');
const swaggerDoc = jsyaml.safeLoad(spec);
const app = express();
if (config.env === 'development') {
app.use(logger('dev'));
}
// parse body params and attache them to req.body
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookieParser());
app.use(compress());
app.use(methodOverride());
// secure apps by setting various HTTP headers
app.use(helmet());
// enable CORS - Cross Origin Resource Sharing
app.use(cors());
// enable detailed API logging in dev env
if (config.env === 'development') {
expressWinston.requestWhitelist.push('body');
expressWinston.responseWhitelist.push('body');
app.use(expressWinston.logger({
winstonInstance,
meta: true, // optional: log meta data about request (defaults to true)
msg: 'HTTP {{req.method}} {{req.url}} {{res.statusCode}} {{res.responseTime}}ms',
colorStatus: true // Color the status code (default green, 3XX cyan, 4XX yellow, 5XX red).
}));
}
// mount all routes on /api path
app.use('/v1', routes);
// swagger ui config
app.use('/', swaggerUi.serve, swaggerUi.setup(swaggerDoc, false, {}, '.swagger-ui .topbar { background-color: rgb(112, 111, 111); }'));
// if error is not an instanceOf APIError, convert it.
app.use((err, req, res, next) => {
if (err instanceof expressValidation.ValidationError) {
// validation error contains errors which is an array of error each containing message[]
const unifiedErrorMessage = err.errors.map(error => error.messages.join('. ')).join(' and ');
const error = new APIError(unifiedErrorMessage, err.status, true);
return next(error);
} else if (!(err instanceof APIError)) {
const apiError = new APIError(err.message, err.status, err.isPublic);
return next(apiError);
}
return next(err);
});
// catch 404 and forward to error handler
app.use((req, res, next) => {
const err = new APIError('API not found', httpStatus.NOT_FOUND);
return next(err);
});
// log error in winston transports except when executing test suite
if (config.env !== 'test') {
app.use(expressWinston.errorLogger({
winstonInstance
}));
}
// error handler, send stacktrace only during development
app.use((err, req, res, next) => // eslint-disable-line no-unused-vars
res.status(err.status).json({
message: err.isPublic ? err.message : httpStatus[err.status],
stack: config.env === 'development' ? err.stack : {}
})
);
export default app;
index.route.js
import express from 'express';
import userRoutes from './user.route';
import authRoutes from './auth.route';
const router = express.Router(); // eslint-disable-line new-cap
/** GET /health-check - Check service health */
router.get('/health-check', (req, res) =>
res.send('OK')
);
// mount user routes at /users
router.use('/users', userRoutes);
// mount auth routes at /auth
router.use('/auth', authRoutes);
export default router;
I'm having a hard time understanding what is going on there because I don't see any app.get(), but the way Express works is it sends the first matching route, so you may need to install a splat route at the end of your list of routes:
// Splat Route
app.get('*', function(req, res) {
// res.send('Sorry, page not found.');
res.render('404');
});
You can convert that to fat arrow if you want. I'm just looking a bit closer and I see: import routes from '../server/v1/routes/index.route';
Inside that file, try putting the splat at the bottom. It's like a catch-all if none of the other routes match.