Ok So I have a simple node.js / express.js / mongodb app set up here with my config as follows.
var express = require('express'),
mongoose = require('mongoose');
http = require('http');
var app = express();
app.configure(function(){
app.set('port', process.env.PORT || 3000);
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
//middleware stack
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(__dirname + "/public"));
});
mongoose.connect("mongodb://localhost/hello");
The problem lies when I try to make PUT or DELETE requests. My form is this simple
<form method="POST" action="/users/#{user.name}">
<input type="hidden" name="_method" value="PUT"/>
</form>
Now my router catches the route with the express .put() method
app.put('/users/:name', function(req, res) {
var b = req.body;
Users.update(
{ name: req.user.name },
{ name: b.name, age: b.age, email: b.email },
function(err) {
res.redirect('/users/'+b.name);
});
})
When I make the request I simply get a "Cannot PUT" or "Cannot DELETE" error.
I have tried to make this same request via chomes RESTful client with the same result.
I have read a topic witch has the same problem as me although following the comments the answers did not solve my problem.
Questions I have looked into
expressjs support for method delete and put without the methodoverride
Are the PUT, DELETE, HEAD, etc methods available in most web browsers?
Along with a few others.
I have also referenced the express.js and mongo documentation several times.
I just cant think what could be going wrong.
Any help is appreciated.
Update
As Jonathan Lonowski pointed out PUT can also be used, so you can ignore my old answer.
Getting Cannot PUT or Cannot POST errors, means your callback is not executing successfully. My guess is that Users.update is failing, which is why it cannot POST or PUT. Can you check it.
Old answer
Try changing this line
app.put('/users/:name', function(req, res) {
to
app.post('/users/:name', function(req, res) {
since you are trying to submit the form
Is the <form> you listed in a view or a static file under __dirname + "/public"?
Within a static file, the #{user.name} probably isn't being replaced with the user's name and will be treated as a URL Fragment.
The <form> will actually submit to /users/ rather than /users/:name since that's the path:
console.log(url.parse('/users/#{user.name}'));
{ hash: '#{user.name}',
pathname: '/users/',
path: '/users/',
href: '/users/#{user.name}' }
The <form> should be generated from a view if it isn't since the action needs to be dynamic and data-driven. With Jade and assuming user is a member of locals, that would be:
form(method='POST', action='/users/' + user.name)
input(type='hidden', name='_method', value='PUT')
Unless there is strange magic at work, your form makes a POST request, not a PUT. If you want to PUT, I would suggest using the jQuery.ajax function with a type: 'PUT' parameter, like this answer, from a form handler, see jQuery.submit. Don't forget to return false so that the form doesn't submit twice.
If your using method override, make sure you have declared it before you use your routes. That was the problem I was having.
app.post("/movies/:id") is one solution.
If you still want to use app.put("/movies/:id") then try this:
Install method-ovveride from npm.
Require it in your app.js file.
Open the form from where you wanna invoke PUT request
make sure your form has the following attributes:
action="/movies/<%= movies._id %>?_method=PUT " method="POST" >
These two solutions worked for me. If you are following REST, then use the method-ovveride else app.post() will also do the trick
Change res.redirect('path') to res.redirect(303, 'path')
In Put and Delete, if you want to redirect to get address, you should pass 303 as first parameter. (source)
one solution is to use cors middleware for you PUT,PATCH and DELETE requests like this in your app.js file like this:
first install the cors package via npm :
npm i cors
then add the following code to your app.js:
const cors = require('cors')
app.use(cors())
Related
I'm trying to make a website using NodeJS with express and pug for templates.
I integrated Pug using the express docs, but I keep getting TypeError: View is not a constructor on the render function. I really looked, but the only people who had this problem got it with other templates, and were not doing it has simple.
So at the end I did it like this :
var express = require('express');
var server = express();
server.set('views', './views');
server.set('view engine', 'pug');
server.get('/', (req, res) => {
res.render('example', {title: "code", message: "message"});
});
require('./settings')(server); console.log('Settings initialized');
server.listen(server.settings.port);
console.log('Server listening on port', server.settings.port);
And in views/ the template looks like :
html
head
title = title
body
h1 = message
In package.json, I imported it like : "pug": "^2.0.0-rc.1"
Thank you for your tips ! And have a nice day.
-- EDIT --
So I made a little experiment by uninstalling pug ... I have the same result, so I guess I didn't install or parametrized well pug.
-- EDIT --
Made some other tests and it turns out without the require it works well. But I don't see why, and I need to import external files.
This could be happen for various reason one of the most frequent is that there is a typo in the way you use pug in your index.js or where the object is instanced.
After made sure to have installed it locally( check your package.json) and to have created a root a folder called "views", and in there same file that you use in your rooter, then to be sure to use the sintax:
server.set('view engine', 'pug');
server.set('views','./views');// default, but if you specify don't make mistake on this
In your case I believe it is the order that caused this error.
Another note, useful is that the pug sintax, is required that "tags" are followed without space by "=", like:
html
head
title= title
body
h1= message
I had the same error because of a typo in views
server.set('view', './views');
server.set('view engine', 'pug');
I fixed it by putting an s to views
server.set('views', './views');
(this discussion assumes app is express: var app = express();)
You're getting this error because you're stepping on app.settings, which contains a view property.
I understand the appeal of wanting to call app.settings, so you can use app.settings.custom instead, and it won't cause a clash:
settings.ts
module.exports = {
port: 3000
}
app.ts
// settings
app.settings.custom = require('./settings')
...
var server = await app.listen(app.settings.custom.port, async () => {
var address = server.address()
console.log(`app.js - server started at ${new Date().toLocaleTimeString()} in ${app.environment} - http://${address.address}:${address.port}`)
})
If you don't like .custom, you can use Object.assign(app.settings, mySettings), just make sure no property values clash.
I got the following error when running the project from GitHub: "options found in locals object. The option(s) is copied to the option object. This behavior is deprecated and will be removed in EJS 3"
I tried to update the ejs and express modules to the newest versions but the notice persists. I googled, ofc, and the only thread about it is this, but it doesn't help.
Does anyone know more about this?
For reference, here is the whole important code:
app/views/index.ejs
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
</head>
<body>
<h1><%= title %></h1>
<img src="img/logo.jpg" alt="Hack Hands logo">
</body>
</html>
app/controllers/index.server.controller.js
exports.render = function(req, res) {
res.render('index', {
title: 'MEAN MVC'
});
};
app/routes/index.server.route.js
module.exports = function(app) {
var index = require('../controllers/index.server.controller');
app.get('/', index.render);
};
app/config/express.js
var express = require('express');
module.exports = function() {
var app = express();
app.set('views', './app/views');
app.set('view engine', 'ejs');
require('../app/routes/index.server.routes.js')(app);
app.use(express.static('./public'));
return app;
};
server.js
var port = 1337;
var express = require('./config/express');
var app = express();
app.listen(port);
module.exports = app;
console.log('Server running at http://localhost:' + port);
tl;dr: Upgrade to the latest version of EJS. It removes all warnings about options and locals.
whoami
I'm a collaborator (or the collaborator in #micnic's comment above) in EJS v2. I only started maintaining EJS after version 2.0.3 (or something like that) was released, so I don't know a lot about how the API changes took place.
History
EJS v2's renderFile function, used by Express.js, now has the signature
function (path[, options[, locals]], cb)
But for compatibility with and Express.js, which calls all functions as
function (path, locals, cb)
with options mixed into the locals object, EJS automatically picks out the locals with option-y names and treat them as options.
But because the Express.js signature is also the function signature of EJS v1, we also print a warning if any option in locals is copied to options, urging developers to use the new signature with locals and options separated (it was actually me who added the warning).
However, Express.js users do not have a choice in terms of calling convention, so the warning is always present in Express.js.
Some users did complain: #34 #36.
At first, #mde (who is the main maintainer of EJS) pushed a fix, which correctly disables warnings on Express.js, and Express.js only.
But then, the person in #36 still complained, as he was using filename as the name of a local, and when the optiony local is copied to options a warning is printed.
At last, #mde was like "f*** this shit" and removed all the deprecation warnings, including an uncontroversial and legitimate one, and released version 2.2.4 (the legitimate warning was restored by me after the release).
Future
#dougwilson (an Express.js maintainer) said he was interested in a separation of options and locals in Express.js v5, just like in EJS v2. I did volunteer to make that change, but then I got busy so yeah.
Hi everyone i am new to node.js and express. i am just experimenting with various node.js code from various tutorials.
As per the official npm website correct syntax to use methodOverride is
// Be sure to place after the body parser if you want to accept the method
// override using a post parameter
app.use(express.bodyParser());
// Accepts a single argument, the name of the method override parameter,
// defaults to "_method"
app.use(require('express-method-override')('method_override_param_name'));
But when i used this, i got the following error
Error: Most middleware (like bodyParser) is no longer bundled with Express and
ust be installed separately. Please see https://github.com/senchalabs/connect#m
ddleware.
at Function.Object.defineProperty.get (E:\node_modules\npm\node_modules\exp
ess\lib\express.js:89:13)
As far as i researched app.use(express.bodyParser()) is deprecated. Express no longer includes the bodyParser middleware. So to my guess app.use(bodyParser()) is right and i altered my code like this
app.use(bodyParser());
app.use(require('express-method-override')('method_override_param_name'));
Below is my put functionality code
app.put('/user/:id', function(req, res){
console.log('Sha Put testing');
console.log(req.body);
//user.findByIdAndUpdate({email: req.params.id},
user.update({email: req.params.id},
{
email: req.body.email,
name: req.body.name,
age : req.body.age
},
function(err, docs){
if(err) res.json('Error here paiyaa -->' + err);
else
{
console.log(docs);
res.redirect('/user/'+req.body.email);
}
});
});
When i replace app.put with app.post it works fine. But my task is to achieve PUT functionality.As mentioned in express-method-override source, I have used hidden field with the name _method, which helps to override POST method, and facilitates PUT method.
My Edit form code is
<h1>Editing #{user.name}'s profile!</h1>
form(method="POST", action="/user/#{user._id}")
input(type="hidden", name="_method", value="PUT")
p Name:
input(type="text", name="name", value="#{user.name}")
p Age:
input(type="number", name="age", value="#{user.age}")
p
input(type="submit")
When i run the above code it throws below error while submitting the form
Cannot POST /user/test#gmail.com
Can some expert help me to fix this and understand bit clear please
You have a PUT route defined with app.put, but are trying to POST to it. The verbs need to match up, so either your request should be a PUT (makes sense for your user.update method) or change the route to app.post.
EDIT:
Looking at the source for express-method-override, your request body needs to have a _method: 'PUT' (as a default -- currently you are passing 'method_override_param_name' property on it in order for the middleware to override the POST verb.
If you are not already, you should also be including the body-parser middleware. npm install body-parser on the commandline or add it to your package.json and run npm install. var bodyParser = require('body-parser'); will then give the rest of your code what it needs to include the middleware.
i'm trying to get forms working in my express app. i have a middleware function that passes the csrf token, req.session._csrf, to res.locals.csrf_token, so the view can use it. now i'm trying to use the local variable in my view and i'm getting a forbidden error from my session middleware.
here's my form code - i'm using handlebars as my templating engine:
<form method='post' action='/api/entries' enctype='multipart/form-data' >
<input type='hidden' name='_csrf' value={{csrf_token}} />
<input class='foo' type='text' />
<input class='bar' type='text' />
<button id='submit' type='submit'> SUBMIT
</form>
i've tried referencing the csrf_token variable with and without the double curly braces and neither works. any ideas on what i am doing wrong? the Error: Forbidden happens before my route function for POSTing to /api/entries is even called. so i'm pretty sure the problem is that i'm doing something wrong with referencing the csrf token..
*edit:*in regards to the "req.session._csrf is deprecated, use req.csrfToken() instead" getting logged to the console, i did:
grep -r '_csrf' .
in my app directory. here was the output.. it doesn't look like i'm referencing it anywhere besides the view, where my hidden CSRF field is named "_csrf"..
./node_modules/express/node_modules/connect/lib/middleware/csrf.js: var secret = req.session._csrfSecret;
./node_modules/express/node_modules/connect/lib/middleware/csrf.js: req.session._csrfSecret = secret;
./node_modules/express/node_modules/connect/lib/middleware/csrf.js: Object.defineProperty(req.session, '_csrf', {
./node_modules/express/node_modules/connect/lib/middleware/csrf.js: console.warn('req.session._csrf is deprecated, use req.csrfToken() instead');
./node_modules/express/node_modules/connect/lib/middleware/csrf.js: return (req.body && req.body._csrf)
./node_modules/express/node_modules/connect/lib/middleware/csrf.js: || (req.query && req.query._csrf)
./v/home.hbs: <input type='hidden' name='_csrf' value={{csrf_token}} />
./v/show.hbs: <input type='hidden' name='_csrf' value={{csrf_token}} />
here is the entire error stack i'm getting when trying to POST to the /api/entries endpoint (i stupidly neglected to mention this before, but i'm using connect-redis for session middleware):
Error: Forbidden
at Object.exports.error (appFolder/node_modules/express/node_modules/connect/lib/utils.js:63:13)
at createToken (appFolder/node_modules/express/node_modules/connect/lib/middleware/csrf.js:82:55)
at Object.handle (appFolder/node_modules/express/node_modules/connect/lib/middleware/csrf.js:48:24)
at next (appFolder/node_modules/express/node_modules/connect/lib/proto.js:193:15)
at next (appFolder/node_modules/express/node_modules/connect/lib/middleware/session.js:318:9)
at appFolder/node_modules/express/node_modules/connect/lib/middleware/session.js:342:9
at appFolder/node_modules/connect-redis/lib/connect-redis.js:101:14
at try_callback (appFolder/node_modules/redis/index.js:580:9)
at RedisClient.return_reply (appFolder/node_modules/redis/index.js:670:13)
at ReplyParser.<anonymous> (appFolder/node_modules/redis/index.js:312:14)
edit 2: the error in connect-redis.js is a function trying to get the current session by the session ID and failing. don't know why this would be happening, my connect-redis setup looks correct. this is killing me
EDIT: If you don't need file uploads, don't use the multipart/form-data enctype. Switching to the default enctype would allow express.csrf() to parse the _csrf token.
In order to parse forms with the multipart/form-data enctype, you need use a multipart parser in your app configuration, or handle file uploads yourself. It's recommended to avoid using the included express.bodyParser() and instead use something like busboy or formidable on the routes you're expecting file uploads, to prevent an exploit.
If you go this route, your _csrf field will no longer be caught by express.csrf() because the form body will not be parsed until after the request passes that middleware. Set your form action to '/api/entries?_csrf={{csrf_token}}' to get around this.
var fs = require('fs');
var async = require('async');
var express = require('express');
var formidable = require('formidable');
var app = express();
app.use(express.urlencoded())
.use(express.json())
.use(express.cookieParser())
.use(express.session())
.use(express.csrf())
app.get('/upload', function(req, res) {
// File uploads ignored.
res.render('upload', {_csrf:req.csrfToken()});
});
app.post('/upload', function(req, res) {
// Explicitly handle uploads
var form = new formidable.IncomingForm();
form.uploadDir = 'temp';
var count = 0;
var maxAllowed = 10;
form.onPart = function(part) {
if (!part.filename) return form.handlePart(part);
count++;
// Ignore any more files.
if (count > maxAllowed) return part.resume();
form.handlePart(part);
};
form.parse(req, function(err, fields, files) {
// Process the files. If you don't need them, delete them.
// Note that you should still reap your temp directory on occasion.
async.map(Object.keys(files), function(key, cb) {
fs.unlink(files[key].path, cb);
}, function(err) {
res.end();
});
});
});
CSRF syntax has changed slightly in the latest versions of Express/Connect. You now want your middleware to look like this:
.use(express.csrf())
.use(function (req, res, next) {
res.cookie('XSRF-TOKEN', req.csrfToken());
res.locals.csrftoken = req.csrfToken();
next();
})
For testing your code, note that you first need to GET the form page in order to generate the CSRF token. Only then will your POST succeed. If it fails, you need to reload the page in the browser before trying to POST again.
I too hit this problem today and it's taken several hours for me to find a solution. Hopefully this answer helps someone with my exact problem. As #amagumori, I'm using redis for session handling and express 3.4.8, connect-redis 1.4.7.
Basically I was able to determine that the order of my express configuration affects the number of times a new token was issued. It seemed like everything being served out of public was creating a new token.
Specifically in my case I had to move the calls
app.use(express.methodOverride());
app.use(express.bodyParser());
app.use(express.static(__dirname + '/public'));
above
app.use(express.csrf());
app.use(function(req, res, next){
res.locals.token = req.csrfToken();
next();
});
and tokens are issued as expected for sessions.
I'm trying to have one route cover everything under /foo including /foo itself. I've tried using /foo* which work for everything except it doesn't match /foo. Observe:
var express = require("express"),
app = express.createServer();
app.get("/foo*", function(req, res, next){
res.write("Foo*\n");
next();
});
app.get("/foo", function(req, res){
res.end("Foo\n");
});
app.get("/foo/bar", function(req, res){
res.end("Foo Bar\n");
});
app.listen(3000);
Outputs:
$ curl localhost:3000/foo
Foo
$ curl localhost:3000/foo/bar
Foo*
Foo Bar
What are my options? The best I've come up with is to route /fo* which of course isn't very optimal as it would match way too much.
I think you will have to have 2 routes. If you look at line 331 of the connect router the * in a path is replaced with .+ so will match 1 or more characters.
https://github.com/senchalabs/connect/blob/master/lib/middleware/router.js
If you have 2 routes that perform the same action you can do the following to keep it DRY.
var express = require("express"),
app = express.createServer();
function fooRoute(req, res, next) {
res.end("Foo Route\n");
}
app.get("/foo*", fooRoute);
app.get("/foo", fooRoute);
app.listen(3000);
The connect router has now been removed (https://github.com/senchalabs/connect/issues/262), the author stating that you should use a framework on top of connect (like Express) for routing.
Express currently treats app.get("/foo*") as app.get(/\/foo(.*)/), removing the need for two separate routes. This is in contrast to the previous answer (referring to the now removed connect router) which stated that "* in a path is replaced with .+".
Update: Express now uses the "path-to-regexp" module (since Express 4.0.0) which maintains the same behavior in the version currently referenced. It's unclear to me whether the latest version of that module keeps the behavior, but for now this answer stands.
It is not necessary to have two routes.
Simply add (/*)? at the end of your path string.
For example, app.get('/hello/world(/*)?' /* ... */)
Here is a fully working example, feel free to copy and paste this into a .js file to run with node, and play with it in a browser (or curl):
const app = require('express')()
// will be able to match all of the following
const test1 = 'http://localhost:3000/hello/world'
const test2 = 'http://localhost:3000/hello/world/'
const test3 = 'http://localhost:3000/hello/world/with/more/stuff'
// but fail at this one
const failTest = 'http://localhost:3000/foo/world'
app.get('/hello/world(/*)?', (req, res) => res.send(`
This will match at example endpoints: <br><br>
<pre>${test1}</pre>
<pre>${test2}</pre>
<pre>${test3}</pre>
<br><br> Will NOT match at: <pre>${failTest}</pre>
`))
app.listen(3000, () => console.log('Check this out in a browser at http://localhost:3000/hello/world!'))
In array you also can use variables passing to req.params:
app.get(["/:foo", "/:foo/:bar"], /* function */);
For those who are learning node/express (just like me): do not use wildcard routing if possible!
I also wanted to implement the routing for GET /users/:id/whatever using wildcard routing. This is how I got here.
More info: https://blog.praveen.science/wildcard-routing-is-an-anti-pattern/