I am new to Full Stack web development. Right now I encountered a problem. I am using mongodb, mongoose, node and express.
I have two controllers, one is signup, and another one is profile.
code for signup will be like this
router.post('/',(req,res,next)=>{
let user = new userProfile();
user.email = req.body.email;
user.name = req.body.firstName;
user.save((err)=>{
if(err){
return err;
}
})
res.redirect('/profile');
});
After click submit, I can find the user entry in the collection. Now I am at profile controller.
The code for profile:
router.get('/',(req,res)=>{
console.log(req.body.email);
userProfile.find({email: 'yyyy'},(err,user)=>{
if(err) return err;
})
res.render('profile/profile');
})
When I do the console.log, it outputs undefined. The expected output should be the email address from signup page, so that when I do find function, I will get the correct entry with same email address and display corresponding username on the webpage.
My question is how can profile controller access the same post infor from signup controller.
I assume, that you use a middleware like bodyParser in Express. Otherwise your post request with req.body wouldn't work.
In a router.get() request you do not have any body. You can use a route like this:
http://yourdomain.com/api/profile/test#mail.com with:
router.get('/profile/:email',(req,res)=>{
console.log(req.query.email);
userProfile.find({email: req.query.email},(err,user)=> {
[...]
}
or with params:
http://yourdomain.com/api/profile/?email=test#mail.com with:
router.get('/profile/',(req,res)=>{
console.log(req.params.email);
userProfile.find({email: req.params.email},(err,user)=> {
[...]
}
What your looking for is called Express middleware.
There's a great article here that gives an in-depth explanation with tons of examples of middleware on the application-level and
router-level.
In your case, your Express routing configuration should look something like the following,
// Controller containing your router.get (signup) and router.post (show profile) code
var core = require('../controllers/core.controller');
app.route('/signup')
.post(core.signup, core.showProfile)
You can see that we've delegated the POST /signup path to first call core.signup, then core.showProfile (only if next() is called).
The two endpoint handlers you included in your original post will now be something like the following,
export.signup = (req,res) => {
let user = new userProfile();
user.email = req.body.email;
user.name = req.body.firstName;
user.save((err)=>{
if(err){
return err;
}
})
next();
});
export.showProfile = (req,res ) => {
console.log(req.body.email);
userProfile.find({email: 'yyyy'},(err,user)=>{
if(err) return err;
})
res.render('profile/profile');
})
These would live in whichever file you imported in your app route, which for example, I imported from path ../controllers/core.controller.
Related
I am playing around with this library and I am experiencing an annoying scenario which I believe comes from some sort of conflict in cookies or headers authentication.
When I login to one account everything works great. But then when trying to login to another account, it simply ignore the new data provided and move through the authentication with the old data and connecting to the old account. No matter if the email or the password even exist. (Tried also with fake data).
The library doesn't have proper logout method which make sense, you dont really need one because when you run it simply using node on your machine without any server involved and there is no cookies or any kind of data in memory, everything work great. I can login to as many account as I want.
The problem is when running it on an Express server.
CODE:
// api.js
const OKCupid = require("./okc_lib");
const Promise = require("bluebird");
const okc = Promise.promisifyAll(new OKCupid());
async function start(req, res, next) {
const {
body: {
username,
password
}
} = req;
try {
await okc.loginAsync(username, password);
okc.search(
searchOpt,
(err, resp, body) => {
if (err) return console.log({ err });
const results = body.data;
// do dsomething with results
return res.status(200).json({ message: "OK" });
});
}
);
} catch (error) {
res.status(400).json({ message: "Something went wrong", error });
}
}
module.exports = { start };
// routes.js
const express = require("express");
const router = express.Router();
const { start, login } = require("../actions/okc");
router.post("/login", login);
router.post("/start", start);
module.exports = router;
So when trying first to post to url/login it works fine. But when you try to do it again with different username and password it simply go through and ignore the new data and connect to the old one.
As part of my investigation I looked at the source code of the library and found a method clearOAuthToken which clear the token from the header. However it didn't really do anything. So I tried to remove the jar initialisation from the requester helper and it was the only thing that helped me to move on and login to another account. BUT it was only for experimenting and cant be a solution as you do need those cookies for other parts of the library. It was only a proof the problem isn't in the headers but in the cookies.
Any idea how can I "reset" state of server between each call?
"when trying to login to another account, it simply ignore the new data provided and move through the authentication with the old data and connecting to the old account."
As OP mentioned in the comment, this is not an authorization header issue, but a cookie issue.
To implement the logout interface, you can manually clear the cookies:
OKCupid.prototype.logout = function(){
request = request.defaults({jar: request.jar()}) // reset the cookie jar
headers.clearOAuthToken(); // just in case
}
I'm trying to add a new route to fetch a user by id but my error handling is not working correctly. Here is the code for that route.
const express = require('express');
require('./db/mongoose');
const User = require('./models/user');
const Task = require('./models/task');
const app = express();
const port = process.env.PORT || 3000;
app.use(express.json());
// ***removed code for brevity
// Route for fetching user by id
app.get('/users/:id', (req, res) => {
//console.log(req.params.id);
const _id = req.params.id;
User.findById(_id)
.then(user => {
//console.log(user)
if (!user) {
return res.status(404).send();
}
res.send(user);
})
.catch(e => {
res.status(500).send();
});
});
So if I test the route on Postman and I enter the correct user id from the database I get that user sent back, which is the the correct response. But if I enter an incorrect user id I get the 500 error code response instead of the 404 error code. The if (!user) statement is getting skipped and I can't figure out why. Any thoughts as to what I am missing?
Running this thru my own personal mongoose/express-using project, I get the following error:
UnhandledPromiseRejectionWarning: CastError: Cast to ObjectId failed for value "12345" at path "_id" for model "User"
That basically means Mongoose is expecting its own specific object type, an "ObjectId". This is a bit of a pain, since normally if you're using .findOne({_id:something), you can just use a string. If we do:
User.findById(mongoose.Types.ObjectId(_id))
it should work. Note that if you use an invalid id (like I obviously did here, it'll still error out. For that reason, I'd use the standard NodeJS format for callbacky stuff:
.then((err,result)=>{
//other stuff
});
In general, the .catch() block should only happen if obviously Mongoose and your router can't handle it.
EDIT: Also, for others info, Mongoose.model.findById is a built-in convenience method, and should basically do exactly what it says on the tin.
I started working on a MERN App today and am trying to write a restful api. First I am using mlab to store my mongodb database. I have succesfully connected to this database after creating a user. I can manually create a collection and inject some data into this collection. From my server.js file I can then get the data stored in here.
MongoClient.connect(db_url, (err, database) => {
if (err) return console.log(err);
var collection = database.collection('memories'); // Collection called memories
app.listen(3000, () => {
console.log("Listening on 3000");
});
});
Thats all fine and dandy but I want to take it to the next level. I want to write a CRUD api for the collection Memory. Coming from django, I would like to create my model first. Therefore, in my models/memory.js:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var MemorySchema = new Schema({
name: String,
description: String
});
module.exports = mongoose.model('Memory', MemorySchema);
Then I went ahead and started working on my routes/api/api.js:
let router = require('express').Router();
let Memory = require('../../../models/memories');
router.use(function (req, res, next) {
console.log("Something is happening");
next(); // Request stops at middleware without next()
});
router.route('/memory')
.post(function (req, res) {
let memory = new Memory();
memory.name = req.body.name;
memory.description = req.body.description;
memory.save(function (err) {
if (err) {
res.send(err);
}
res.json({message: 'Memory Created'});
});
})
.get(function (req, res) {
res.json({message: 'First memory'});
});
module.exports = router;
And in my server.js I call this module:
const apiRoutes = require('./routes/api/api');
app.use('/api/', apiRoutes);
However, after testing the post api with postman, it the POST request just takes forever before showing up as Could not get any response. However, the GET request works. What am I missing?
EDIT: So the post function is having trouble saving the model instance...
Try adding results as the first parameter in the callback of the save function, then res.json(results, { message: "Memory Created" }) to see if you are returned anything.
The main difference between the post and the get method is that the post method uses Mongoose, while the get doesn't. If you fail to connect to the database then the response can time out due to memory.save(...) not working as it should. And there are no responses sent outside the callback to save, so if your program never enter it, you will never send a response. The request will time out eventually.
In your model file you register a model on the following line:
module.exports = mongoose.model('Memory', MemorySchema);
Mongoose will then look for data in the memorys collection. If you change it to
module.exports = mongoose.model('Memory', MemorySchema, 'memories');
it will use the memories collection instead. This will make it consistent with the connection-to-db snippet you posted. I don't know if that will fix your issue though. I would suggest changing the connection code to
mongoose.connect(dburl, {
useMongoClient: true
});
instead of the native mongo client. You can add these lines too
mongoose.connection.on('connected', function () {
console.log('Mongoose connected');
});
mongoose.connection.on('error', function (err) {
console.log('Mongoose connection error: ' + err);
});
mongoose.connection.on('disconnected', function () {
console.log('Mongoose disconnected');
});
right after the connection code to help with debugging. Make sure you get connected when starting the app.
If you see an error similar to this Error: Can't set headers after they are sent. in the node terminal window, it might be because you are sending two responses in the post function. If an error occurs while saving it will enter the if(err) block, send a response async then go to the res.json(...) response and send that too.
So you have to return after sending the response to exit the function. Either like this
res.send(err);
return;
or like this
return res.send(err);
Same for the json response.
If that doesn't fix the problem you should either fire up the debugger (node --inspect or nodemon --inspect), or insert a console.log('inside post'); inside the post function to see that you're actually entering it.
I'm currently getting started with Sails.js, and I want to add user accounts to my toy app, so I installed the "sails-auth" package that creates a Passport-based user authentication system. I can create new users by sending POST /user, and I can sign in with POST /auth/local.
The documentation says:
Authenticate with the local strategy via a POST to /auth/local with params identifier (email) and password). This will also create a session. See passport.local for more.
However, when I try to GET /user/me, which routes to a controller action that should return the current user in the session, the page instead gives an empty response. Why is this happening? Is there some kind of configuration step that I'm missing?
By the way, I haven't changed or messed around with the sails-auth package. It's still completely new; the "me" action looks like this:
me: function (req, res) {
res.ok(req.user);
}
EDIT: I've found a temporary workaround by searching the issues in the sails-auth repo. Instead of getting a user object from req.user, you can get a string user ID from req.session.passport.user.
Your me action as written is only going to return whatever you are passing in as the user param. Sails builds on top of Express.js so req is the request from the browser and res is the response to the browser.
Most likely you are sending the data to your me action in the req body which is why your response is blank, simply put, req.user is empty so the response is empty. In that case you would access it with req.body.user, you could also try var user = req.params();
For debugging and just generally getting a feel for how the req and res objects are structured I suggest you always start sails (in development, never in production) with the verbose flag.
sails lift --verbose
Then you can do this:
me: function(req, res){
sails.log.verbose(req);
res.ok(req.user);
}
And have it print out the entire req object so you know what's in req.user.
Typically though you would do a database lookup as the user param would be an id. Which means your me function might look (something, obviously depending on your dbc it might be pretty different) like:
me: function(req, res){
var userId = req.body.user;
User.find({'user_id': userId}.exec(function(err, user){
if(err){
//tell peeps there was an error
}else{
res.ok(user);
}
});
}
Best debugging for routes and for the request object:
'/*' : function(req, res, next) {
sails.log.verbose("method: ", req.method, "\n body: ", req.body, "\n url:", req.url);
next();
},
Just paste that at the start of your routes module.
I'm in charge of setting up profile pages for each user. I have set up a way to route through to "../profile/:username", but I cannot figure out how to actually render a unique page for each user.
Here is my get from index.js
router.get('/profile/:userName', function (req, res) {
//find user object from database
var userName = req.params.userName;
var userObject = userlistdb.find({ username: userName });
//route through to the user's unique profile page
res.render('profile/' + userObject.username, { ObjectId: userObject._id});});
My profile.jade is pretty abysmal so I won't bother sharing.
Our app is connected to a remote Mongo database and as far as I can tell it's being called correctly. When I route through to a user page from our db, I get the error "Failed to lookup view "profile/undefined" in views directory". Because there is no /profile/[username].jade file for any user this isn't working.
How can I get around this issue? Do I need to create a new jade file for each user?
Thanks
You cannot render file profile/:userId as it doesn't exist, so
res.render('profile/' + userObject.username)
gonna throw exception, but what you can do is render profile_page for a specific user.
use callback on database calls,
create 2-3 views(error.jade, profile_not_found.jade, profile_page.jade)
And router should look like
router.get('/profile/:userName', function (req, res) {
//find user object from database
var userName = req.params.userName;
//findOne will return single user instance
userlistdb.findOne({username: userName}, function(err, user){
if(err){
//render some error page
//pass error, populate view, or just send InternalServerError(500)
res.render('error', {err: err});
} else if (!user){
//render 404 or more specific page
//pass not found username
res.render('profile_not_found', {username: username});
} else {
//render user page
//route through to the user's unique profile page
res.render('profile_page', {user: user}); //pass user, populate view
}
});
});