I read this post AsyncLocalStorage for Easy Context Passing in Node.js
I try to get logId in my logs, but i cant, beacause asyncLocalStorage.getStore() return undefined.
It seems that context was lost inside MyLogger class.
How to solve it?
Here is my express App
const asyncLocalStorage = new AsyncLocalStorage();
app.use((req, res, next) => {
asyncLocalStorage.run(new Map(), () => {
asyncLocalStorage.getStore().set("requestId", uuid());
next();
});
});
module.exports.asyncLocalStorage = asyncLocalStorage;
Here is MyLogger class
static log(logId, className, text) {
const { asyncLocalStorage } = require("../server.js");
const store = asyncLocalStorage.getStore()
console.log(this._getBaseStaticLog(logId, logTypes.LOG, text, className));
}
I solve the problem.
The problem was that i lose context due to bodyparser middleware.
I change that middleware before set context, and now its ok.
Was:
app.use((req, res, next) => {
asyncLocalStorage.run(new Map(), () => {
asyncLocalStorage.getStore().set("requestId", uuid());
next();
});
});
// body parser
app.use(
bodyParser.json({
limit: "10mb"
})
);
Change:
// body parser
app.use(
bodyParser.json({
limit: "10mb"
})
);
app.use((req, res, next) => {
asyncLocalStorage.run(new Map(), () => {
asyncLocalStorage.getStore().set("requestId", uuid());
next();
});
});
And now its ok)
Related
How to implement middleware like this in socket.io? Please help
EXPRESS APP
var myLogger = function (req, res, next) {
console.log('LOGGED')
next()
}
app.use(myLogger)
app.get('/', function (req, res) {
res.send('Hello World!')
})
SOCKET APP (I am using express pattern but its not working)
var myLogger = function (data,next) {
console.log('DOING DATA VALIDATION...')
next()
}
io.use(myLogger)
io.on('someEvent/', function (data, callback) {
callback('Hello World!')
})
Error : next() is not define!
app.use(function (req, res, next) {
req.io = io;
next();
});
This assigns a socket object to every request.
If somebody's still wondering.
To use middleware on all sockets:
io.use((socket, next) => {
// isValid is just a dummy function
if (isValid(socket.request)) {
next();
} else {
next(new Error("invalid"));
}
});
This example is from the official docs of socket.io
To use a middleware for a specific client:
io.on('connection', async (client) => {
client.use((socket, next) => {
console.log(`got event: ${socket[0]} in client middleware, moving on with next() just like in express`)
next()
});
// rest of your code
newConnection(client)
})
I am learning a new way to authenticate all my APIs using the application-level middleware. I looked into multiple examples. I tried the following code as one of the ways.
Below is my code, I am writing firebase function with the help of necessary fields already there. I use "firebase serve" to host my functions locally.
const express = require('express')
const bodyParser = require('body-parser')
const cookieParser = require('cookie-parser')()
const cors = require('cors')({ origin: true })
const app = express()
const router = express.Router()
app.use(cors)
app.use(cookieParser)
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false }))
app.use(async (err, req, res, next) => {
console.log('Middleware')
try {
const { authorization } = req.headers
if (!authorization) {
throw new ErrorHandler(401, "User is not unathorized")
}
if (!authorization.startsWith('Bearer')) {
throw new ErrorHandler(401, "User is not unathorized")
}
const split = authorization.split('Bearer ')
if (split.length !== 2) {
throw new ErrorHandler(401, "User is not unathorized")
}
const token = split[1]
const decodedToken = await admin.auth().verifyIdToken(token);
res.setHeader("email", decodedToken.email)
next()
} catch (error) {
console.log("END")
next(error)
}
});
router.get('/', (req, res) => {
res.end(`${Date.now()}`)
})
router.post('/data', async (req, res, next) => {
res.setHeader("Content-Type", "application/json")
console.log('DATA')
try {
// my other logic goes here
res.end()
} catch (error) {
next(error)
}
})
app.use('/api', router)
app.use((err, req, res, next) => {
if (err) {
handleError(err, res);
}
console.log(JSON.stringify(req.body))
});
exports.app = functions.https.onRequest(app)
I have created a cloud function named app. I am using API like this:
http://localhost:5000/app/api/data
I have written a middleware for authorizing all my APIs that are coming. Middleware is fetching bearer token and token is being verified with the help of firebase.
But when I call "/api/data" this API from postman or web the middleware is not called. For debugging purpose I used console.log to check.
My current flow is POSTMAN -> DATA
What I want is:
POSTMAN -> MIDDLEWARE(if authenticated) -> DATA
POSTMAN -> MIDDLEWARE(if not authenticated) -> END
Please let me know what is the issue with my code is.
Remove err parameter from the middleware, You are setting it as an error handler instead of a middleware, this is the reason the code is not getting executed,
Below code will execute the handler every time you access the /api route
const express = require('express')
const bodyParser = require('body-parser')
const cookieParser = require('cookie-parser')()
const cors = require('cors')({ origin: true })
const app = express()
const router = express.Router()
app.use(cors)
app.use(cookieParser)
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false }))
app.use(async (req, res, next) => {
console.log('Middleware')
try {
const { authorization } = req.headers
if (!authorization) {
throw new ErrorHandler(401, "User is not unathorized")
}
if (!authorization.startsWith('Bearer')) {
throw new ErrorHandler(401, "User is not unathorized")
}
const split = authorization.split('Bearer ')
if (split.length !== 2) {
throw new ErrorHandler(401, "User is not unathorized")
}
const token = split[1]
const decodedToken = await admin.auth().verifyIdToken(token);
res.setHeader("email", decodedToken.email)
next()
} catch (error) {
console.log("END")
next(error)
}
});
router.get('/', (req, res) => {
res.end(`${Date.now()}`)
})
router.post('/data', async (req, res, next) => {
res.setHeader("Content-Type", "application/json")
console.log('DATA')
try {
// my other logic goes here
res.end()
} catch (error) {
next(error)
}
})
app.use('/api', router)
app.use((err, req, res, next) => {
if (err) {
handleError(err, res);
}
console.log(JSON.stringify(req.body))
});
exports.app = functions.https.onRequest(app)
I just realized an app I have been working on no longer works when I sign in to the application. After I sign in I get a blank page with
this.sessionModel.find is not a function printed at the top with a 500 error in the console
I am deathly afraid there is something wrong with the packages I am using. Has anyone ever experienced this problem before? I'll add some code snippets below, let me know if I need to provide more info thank you so much in advance. Here is how I am setting up the server:
const path = require('path')
const express = require('express')
const morgan = require('morgan')
const bodyParser = require('body-parser')
const compression = require('compression')
const session = require('express-session')
const passport = require('passport')
const SequelizeStore = require('connect-session-sequelize')(session.Store)
const db = require('./db')
const sessionStore = new SequelizeStore({db})
const PORT = process.env.PORT || 8080
const sslRedirect = require('heroku-ssl-redirect')
const app = express()
module.exports = app
/**
this file is where we import middleware, route the routes, sync the db, and start the server.
*/
if (process.env.NODE_ENV !== 'production') require('../secrets')
// passport registration
passport.serializeUser((user, done) => done(null, user.id))
passport.deserializeUser((id, done) =>
db.models.user.findById(id)
.then(user => done(null, user))
.catch(done))
const createApp = () => {
// logging middleware
app.use(morgan('dev'))
// body parsing middleware
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))
// compression middleware
app.use(compression())
// session middleware with passport
app.use(session({
secret: process.env.SESSION_SECRET || 'nothing',
store: sessionStore,
resave: false,
saveUninitialized: false
}))
app.use(passport.initialize())
app.use(passport.session())
// auth and api routes
app.use(sslRedirect())
app.use('/auth', require('./auth'))
app.use('/api', require('./api'))
// static file-serving middleware
app.use(express.static(path.join(__dirname, '..', 'public')))
// any remaining requests with an extension (.js, .css, etc.) send 404
app.use((req, res, next) => {
if (path.extname(req.path).length) {
const err = new Error('Not found')
err.status = 404
next(err)
} else {
next()
}
})
// sends index.html
app.use('*', (req, res) => {
res.sendFile(path.join(__dirname, '..', 'public/index.html'))
})
// error handling endware
app.use((err, req, res, next) => {
console.error(err)
console.error(err.stack)
res.status(err.status || 500).send(err.message || 'Internal server error.')
})
}
const startListening = () => {
// start listening (and create a 'server' object representing our server)
const server = app.listen(PORT, () => console.log(`Serving on port ${PORT}`))
}
const syncDb = () => db.sync()
// This evaluates as true when this file is run directly from the command line,
// i.e. when we say 'node server/index.js' (or 'nodemon server/index.js', or 'nodemon server', etc)
// It will evaluate false when this module is required by another module - for example,
// if we wanted to require our app in a test spec
if (require.main === module) {
sessionStore.sync()
.then(syncDb)
.then(createApp)
.then(startListening)
} else {
createApp()
}
and here are my routes for logging in:
const router = require('express').Router()
const User = require('../db/models/user')
module.exports = router
//routes for login authentication
//login
router.post('/login', (req, res, next) => {
User.findOne({where: {email: req.body.email}})
.then(user => {
if (!user) {
res.status(401).send('User not found')
} else if (!user.correctPassword(req.body.password)) {
res.status(401).send('Incorrect password')
} else {
req.login(user, err => (err ? next(err) : res.json(user)))
}
})
.catch(next)
})
//change password
router.put('/:id/resetpw', (req, res, next) => {
User.update(req.body, {
where: {
id: req.params.id
},
individualHooks: true
})
.then(([updatedRows, [updatedUser]]) => {
res.status(200).json(updatedUser)
})
.catch(next)
})
//sign up
router.post('/signup', (req, res, next) => {
User.create(req.body)
.then(user => {
req.login(user, err => (err ? next(err) : res.json(user)))
})
.catch(err => {
if (err.name === 'SequelizeUniqueConstraintError') {
res.status(401).send('User already exists')
} else {
next(err)
}
})
})
//logout
router.post('/logout', (req, res) => {
req.logout()
req.session.destroy()
res.redirect('/')
})
router.get('/me', (req, res) => {
res.json(req.user)
})
That's usually a sign of attempting to load modules defined as ES6 exports.
For example, to load the latest version of heroku-ssl-redirect in Express (as of now), one must use:
const sslRedirect = require('heroku-ssl-redirect').default;
Notice the .default at the end. Your heroku-ssl-redirect import seems to be working without it, but the latest version I installed today requires the .default otherwise throws the same error, reading in this case sslRedirect is not a function.
I cannot see the code of your models, but in this thread the same approach is used to load models when using Sequelize:
const model = require(modelPath).default(sequelize, Sequelize);
Notice the .default at the model require.
That could be the problem with your erring packages.
I got the following issue: the options variable in the express router is not using the correct options variable. Any suggestions?
router.use('/test1', new Factory("test1") );
router.use('/test2', new Factory("test2") );
function Factory(options) {
router.use((req,res,next) => {
res.json(options)
})
return router
};
/*
Returns :
/test1 "test1"
/test2 "test1" ???
*/
function Factory(options) {
return (req,res,next) => {
res.json(options)
})
};
router.use('/test1', new Factory("test1") );
router.use('/test2', new Factory("test2") );
tl;dr;
Use this instead.
router.get("/:options", Factory);
function Factory(req, res, next){
const options = req.params.options;
res.json(options);
}
Why what you have does not work as expected
In your Factory you do this:
router.use((req, res, next) => {
res.json(options);
});
This statement tells express: hey whenever a request comes in then run this middleware:
(req, res, next) => {
res.json(options);
})
Note that you do not specify a route for this, it's just in the format app.use(handler), so it will run for every request.
above that you do this:
router.get("/test1", new Factory("test30"));
Now when express sees this it says, oh here's a handler for /test1, let me register that handler to that route. When it registers the handler it comes across this expression: new Factory("test1"). Note that this is an expression and will get executed when the route is registered and NOT when handling a request. It can essentially be rewritten like this: router.get("/test1", Factory("test30")), which results in:
router.get("/test1", router.use((req, res, next) => {
res.json("test1");
return router;
}));
This part:
router.get("/test1", )
depends on this part:
router.use((req, res, next) => {
res.json("test1");
return router;
})
Which means that the latter will be executed first. This means that at the end of it all your code actually looks something like this:
const resultOfThisLine =
router.use((req, res, next) => {
res.json("test1"); //You passed test1 as options to the factory
return router;
});
router.get("/test1", resultOfThisLine);
router.get("/test2", router.use((req, res, next) => {
res.json("test1"); //You passed test1 as options to he factory
return router;
}));
As you can see,
router.use((req, res, next) => {
res.json("test1"); //You passed test1 as options to the factory
return router;
});
is actually registered before everything else and since it returns a response, nothing else will ever get called. Moreover, this handler will respond to ANY request because there is no specific url attached to it.
Thanks for the quick reactions. It made me realize I need to create a new router object.
Like so:
const express = require('express')
const router = express.Router();
router.use('/test1', new Factory("test1"));
router.use('/test2', new Factory("test2"));
function Factory(options) {
const router2 = express.Router();
router2.get("", handleRoute.bind({ options: options }))
router2.post("", handleRoute.bind({ options: options }))
function handleRoute(req, res, next) {
res.json({ message: "hello", options: options })
}
return router2
};
I'm trying to unit test this piece of code in Mocha:
app.use(function (err, req, res, next) {
console.error(err.stack)
res.status(500).send('Something broke!')
})
I don't know how to get my request inside a Mocha unit test to trigger it.
First I would break out the middleware into its own file/function. As it sits, it's "integrated" with the Express app. So you're not testings only the error middleware, but also the Express app instance to an extent.
With that said, decouple the error middleware from the Express app:
src/middleware/error-handler.js
module.exports = (err, req, res, next) => {
console.error(err.stack)
res.status(500).send('Something broke!')
}
You will still .use() it in the main app.js or wherever you setup Express:
const express = require('express')
const errorHandler = require('./src/middleware/error-handler')
const app = express()
app.use(errorHandler)
But now we're free of the Express dependency and we have a simple function we can isolate and test. Below is a simple test with Jest which you can easily adjust to work with Mocha.
__tests__/middleware/error-handler.test.js
const errorHandler = require('../../src/middleware')
describe('middleware.ErrorHandler', () => {
/**
* Mocked Express Request object.
*/
let req
/**
* Mocked Express Response object.
*/
let res
/**
* Mocked Express Next function.
*/
const next = jest.fn()
/**
* Reset the `req` and `res` object before each test is ran.
*/
beforeEach(() => {
req = {
params: {},
body: {}
}
res = {
data: null,
code: null,
status (status) {
this.code = status
return this
},
send (payload) {
this.data = payload
}
}
next.mockClear()
})
test('should handle error', () => {
errorHandler(new Error(), req, res, next)
expect(res.code).toBeDefined()
expect(res.code).toBe(500)
expect(res.data).toBeDefined()
expect(res.data).toBe('Something broke!')
})
})
In your route you can pass an error object in the next parameter. For example:
app.get('/some-route', (req, res, next) => {
const error = {....};
next(error);
});
or just throw an error:
app.get('/some-route', (req, res, next) => {
const error = {....};
throw new Error(error);
});