I am wondering how express.js works when you specify multiple argument in the app.get, for example:
app.get('/', requireAuth, (req, res) => { })
Here, I believe you have the slash "/" as the route, and (req, res) as arguments for the callback function. But there is also the 'requireAuth' part which was used for authorization. But in the documentation I don't find anything about multiple arguments in the way we use 'requireAuth' now. It does mention that you can use an array of functions but that is not used here (https://expressjs.com/en/guide/routing.html). How does it work?
P.S. The 'requireAuth' code for reference:
module.exports = (req, res, next) => {
const { authorization } = req.headers;
// authorization === 'Bearer laksjdflaksdjasdfklj'
if (!authorization) {
return res.status(401).send({ error: 'You must be logged in.' });
}
const token = authorization.replace('Bearer ', '');
jwt.verify(token, 'MY_SECRET_KEY', async (err, payload) => {
if (err) {
return res.status(401).send({ error: 'You must be logged in.' });
}
const { userId } = payload;
const user = await User.findById(userId);
req.user = user;
next();
});
};
All the arguments are simply middle-ware functions that you call before the actual route logic is run(for individual routes). As long as there is next handling in each of the middle-ware functions, the chain will run till your route logic, and then exit where next isn't handled.
This is the magic of express. You can do many things with it, like error handling, handling different content types etc.
Related
With express we can use different middlewares for get and post requests,
eg.
// GET method route
app.get('/users', function (req, res) {
// handle get request
})
// POST method route
app.post('/users', auth, function (req, res) {
// handle post request
})
How do I do the same in next js.
I am totally new to next js. I might be simply missing something.
To handle different HTTP methods in an API route, you can use req.method in your request handler.
export default function handler(req, res) {
if (req.method === 'POST') {
// Process a POST request
} else {
// Handle any other HTTP method
}
}
Or you can use a package like next-connect which enables expressjs like API.
In your api file:
import nc from "next-connect";
const handler = nc()
.use(someMiddleware())
.get((req, res) => {
res.send("Hello world");
})
.post((req, res) => {
res.json({ hello: "world" });
})
.put(async (req, res) => {
res.end("async/await is also supported!");
})
.patch(async (req, res) => {
throw new Error("Throws me around! Error can be caught and handled.");
});
export default handler
I'm trying to use passport.authenticate('local') as middleware with passport-local-mongoose but it doesn't seem to pass control to the subsequent middlewares in my route.
I'm assuming there's an error but the following (err, req, res, next) middleware isn't being called so I'm wondering if I've misunderstood something basic about passport-local in general.
I've spent a few hours trying various things, searching here but I can't find a way to isolate my problem any further or get a better log of where control is going wrong in my route.
I've posted a small reproducible example to GitHub.
This is how I'm setting up BEFORE my routes:
// Get connected
mongoose.connect('mongodb://localhost/pass');
mongoose.Promise = global.Promise;
mongoose.connection.on('error', (err) => { console.error(err.message) });
// Basic user schema using nickname field as username
const Schema = mongoose.Schema;
const userSchema = new Schema({});
userSchema.plugin(passportLocalMongoose, { usernameField: 'nickname' });
const User = mongoose.model('User', userSchema);
// Initialise passport before routes
passport.use(User.createStrategy());
passport.serializeUser(User.serializeUser);
passport.deserializeUser(User.deserializeUser);
app.use(passport.initialize());
And this is the route with my passport.authenticate:
app.post('/login',
(req, res, next) => {
console.log('login posted ok');
next();
},
passport.authenticate('local'),
(req, res) => res.send('login successful'),
(err, req, res, next) => {
console.log(err);
res.send('login unsuccessful');
}
);
There are other routes with the pug views and registration.
Registration works fine, in mongo db.users.find() show a good looking entry for the new user.
But my /login post route doesn't get beyond passport.authenticate.
The console.log gets triggered, so I know the route is being called.
Based on my limited knowledge of express and passport, I'm expecting one of those two following middlewares to be triggered, one on success and one if it fails.
Neither is triggering.
Best way to isolate is covered in the authenticate docs under "Custom Callback", I just didn't understand it originally.
NOTE: I've saved the following in the answer branch on my repo, as posted in the question.
app.post('/login',
(req, res, next) => {
console.log('login posted ok');
next();
},
(req, res, next) => {
console.log('authenticating...')
passport.authenticate('local', (err, user, info) => {
console.log(req, res, err, user, info);
next(err);
}) (req, res, next);
},
(req, res) => res.send('login successful'),
(err, req, res, next) => {
console.log(err);
res.send('login unsuccessful');
}
);
What I realised
passport doesn't consider authentication FAIL as an err
it just leaves user null
basically you need to give it success and failure redirects if you want to use middleware form, don't do what I did and try to handle err etc.
came from Java. Was messing around with ExpressJS, I return out of a function after sending a next() if I dont add the return, then code after the next() function still executes when next() is invoked, currently it works, return escapes this behaviour, but I was wondering if this is correct way to do this, or am I developing bad habbits. Nothing is really after the next() in terms of code sequence.
function('/login', (req,res, next) => {
User.findOne({
email: username
}, (err, user) => {
if (user) {
var validPassword = user.comparePassword(password);
if (validPassword) {
let token = Helpers.getJwt(user);
res.send({
success: true,
message: 'Successful Login',
token: token
})
} else {
next(
Boom.badRequest('Invalid Credentials.', {
success: false,
message: 'Credentials did not match our records.'
}));
return;
}
} else {
next(
Boom.badRequest('User not found.', {
success: false,
message: 'User was not found, please register.'
}));
return;
}
});
EDIT: I have middleware called with the next(), my error middleware.
First, You aren't next-ing to anything here(pun intended).
You call middleware:next() function if there is another middleware:function within the chain of a particular route or a group of routes app.use(middleware:function).
Example:
app.get('/user/:id', function (req, res, next) {
console.log('ID:', req.params.id)
next()
}, function (req, res, next) {
res.send('User Info')
})
I'd advice you read on Express Middlewares.
so yeah, you want to check if the user is valid before proceeding with the request.
Below code is just to explain how middle-ware works:
The idea is that, one middleware:function process the request and passes control down to the next function within the chain.
I'm assuming your Boom.badRequest just generates a json payload e.g {};
So this is probably an elegant way to achieve what you want, but im not sure doing this for login is ideal, maybe better for checking if a user's token is valid.
app.post('/login', /*MiddleWare Function*/(req, res, next) => {
User.findOne({email: username}, (err, user) => {
if (user) {
const validPassword = user.comparePassword(req.body.password);
if (!validPassword) return res.status(401).send(Boom.badRequest('User not found.', {
success: false,
message: 'User was not found, please register.'
}));
req.token = Helpers.getJwt(user);
next();// it will move execution to the next function we have below
} else {
res.status(401).send(Boom.badRequest('User not found.', {
success: false,
message: 'User was not found, please register.'
}));
//no need to next here.
}
})
}, /*OUR NEXT FUNCTION*/(req, res) => {
res.send({
success: true,
message: 'Successful Login',
token: req.token//notice that our token can be retrieved here
})
});
So in general, you might just want to have a middleware:function that is called first on a group of specific routes.
app.use(['/users*', '/logout'], (req, res, next) => {
/*
* What this means is that for every request that maps to /users
* or /logout this middleware:function will be called first.
*/
//we can check|test if this visitor has valid credentials.
next();//passes control to app.get('/users/whatEverItIS?') or app.post('/logout');
});
app.post('/users/whatEverItIs', (req, res)=>{
res.send("I passed the middleware test that is why im called");
});
That return statement is unnecessary. Firstly due to the fact that you are not returning anything and secondly because you are passing control to your next middleware using next method, thus that return is useless there and will be returning undefined.
I actually have a couple of questions about Express middleware chaining and thought I'd group them in this single post since they're closely related.
Q1: In a middleware function, if I send() the Response, pass to the next function, or just return, does any of the remaining code get executed?
router.get('/a', (req, res, next) => {
res.sendStatus(200);
// Does this ever get executed?
});
router.get('/b', (req, res, next) => {
next();
// Does this ever get executed?
});
router.get('/c', (req, res, next) => {
return;
// Does this stop the middleware chain? What happens to the request/response?
});
Q2: Can you pass on to another middleware function from inside of the preceding one? My reason for this is that I have to supply an argument to create a certain middleware, and that argument is dependent on which Express app object is the owner of the Request. So I need to access a property of the Request before it is passed to the middleware that needs to be created using that property. Would this work: ?
router.get('/d', (req, res, next) => {
var middlewareFunction = createDynamicMiddleware();
middlewareFunction(req, res, next); // Will this still work as expected?
});
Q3: How are the Request/Response arguments passed to the next middleware when you call next() without any arguments?
Q1: In a middleware function, if I send() the Response, pass to the next function, or just return, does any of the remaining code get
executed?
router.get('/a', (req, res, next) => {
res.sendStatus(200);
// Does this ever get executed?
// Yes, but don't call next() or res.send() or it will throw an error
});
router.get('/b', (req, res, next) => {
next();
// Does this ever get executed?
// Yes, but don't call next() or res.send() or it will throw an error
});
router.get('/c', (req, res, next) => {
return;
// Does this stop the middleware chain? What happens to the request/response?
// The request get stuck and after a while it will fail due 'timeout'
});
Q2: Can you pass on to another middleware function from inside of the preceding one? My reason for this is that I have to supply an
argument to create a certain middleware, and that argument is
dependent on which Express app object is the owner of the Request. So
I need to access a property of the Request before it is passed to the
middleware that needs to be created using that property.
Your factory of dynamic middleware should return a new function
Functions that returns new functions, are also knows as curried functions, here is a practical example:
// based on 'someId' we will return a specific middleware
req.get('/user/:someId',
createDynamicMiddleware,
(req,res) => {
res.send('ok')
})
function createDynamicMiddleware(req,res,next){
if(req.params.someId === 'me') {
return function(req, res, next){
// do some stuff if someId is me
return next();
}
} else {
return function(req, res, next){
// do some other stuff otherwise
return next();
}
}
}
Q3: How are the Request/Response arguments passed to the next middleware when you call next() without any arguments?
Express handle this for you!
Also, if you add a property to req or res objects, it will be present in the next middlewares
For example:
router.get('/', (req,res,next) => {
req.customProperty = 'hello!';
return next();
}, (req, res) => {
console.log(req.myCustomProperty)
// hello!
})
I have been fiddling with this for days, and I cannot figure out why the Mongoose middleware is not being invoked.
So I have an API in node.js and I have a website using Angular.js. The Mongoose middleware is this:
schema.post('remove', function (doc) {
console.log('doctors - post - remove');
});
So this hook is called perfectly fine when invoked from the Angular front end. However, when I run a test with supertest, chai, and mocha the hook is not invoked. Here is my code for the testing:
it('/doctors - POST - (create doctor)', function(done){
request(app)
.post('/doctors')
.send(doctor)
.end(function (err, res){
if (res.body['error']) {
expect(S(res.body['error']).startsWith('doctor already exists')).to.be.true;
}
else
expect(res.body['email']).to.equal(doctor['email']);
done();
});
});
....
it('/doctors/remove - DELETE', function(done){
request(app)
.del('/doctors/remove')
.auth(new_doctor_creds["email"], new_doctor_creds["pass"])
.end(function (err, res){
expect(Object.keys(res.body).length).to.not.equal(0);
done();
});
});
And here is my route for the express app:
app.delete('/doctors/remove', authController.isAuthenticated, function (req, res, next) {
var email = req.user['email'];
Doctors.findOne({email:email}).remove(function (err, removed) {
if (err) return next(err);
return res.status(200).send(removed);
});
});
Again, this Mongoose middleware works perfectly fine when invoked from an API call from the Angular app. However, it does not work when tested with supertest. Any ideas on what to do here?
EDIT: I tried to recreate this example with a simplified version that way you can see all of the code. So here is a two file version that is STILL not working. Here is the app.js:
var mongoose = require('mongoose');
var app = require('express')();
var http = require('http');
var fs = require('fs');
var Doctors = require('./schema');
mongoose.connect('mongodb://localhost/m4', function(err) {
if (err) throw err;
console.log('connected');
app.get('/post', function (req, res, next) {
console.log('create');
Doctors.create({email:"hello"}, function (err, inserted) {
if (err) console.log(err);
res.end();
});
});
app.get('/delete', function (req, res, next) {
console.log('removed');
Doctors.remove({email:"hello"}, function (err, removed) {
if (err) console.log(err);
res.end();
});
});
http.createServer(app).listen('6000', function () {
console.log('now listen on localhost:6000');
});
});
and the schema:
var mongoose = require('mongoose');
var schema = mongoose.Schema({
email: { type: String }
});
schema.pre('save', function (next) {
console.log('doctors - post - save');
next();
});
schema.post('remove', function (doc) {
console.log('doctors - post - remove');
});
module.exports = mongoose.model('Doctors', schema);
Here's what I suggest. Let's perform the #remove on the doc found by #findOne. If I remember correctly, remove post hooks only works on Doc#remove and not on Model#remove.
schema.post('remove', function (doc) {
console.log('doctors - post - remove'); // <-- now runs
});
app.delete('/doctors/remove', authController.isAuthenticated, function (req, res, next) {
var email = req.user['email'];
Doctors.findOne({email: email}, function(err, doc) {
if (err) {
return next(err);
}
doc.remove().then(function(removed) {
return res.status(200).send(removed);
}, function(err) {
next(err);
});
});
});
Mongoose post hooks run AFTER the operation is completed, concurrently with operation callbacks. See the comments below:
Doctors.findOne({email:email}).remove(function (err, removed) {
// All this code and the post hook are executed at the same time
if (err) return next(err);
// Here you send the response so supertest#end() will be triggered
// It's not guaranteed that post remove was executed completely at this point
return res.status(200).send(removed);
});
Post hooks were made to run processes independent of the server response. When you run tests, the server shuts down right after the tests are completed, and maybe it had no time enough to finish the post hooks. In the other hand, when you call the API from a client, normally you keep the server running, so the post jobs can be completed.
Now, there comes a problem: how can we test post hooks consistently? I got up this question because I was looking for a solution to that. If you already have an answer, please post here.