I am a bit stuck trying to test some Express Routes with SuperTest.
// Mocha test
..
request(app)
.get('/user')
.expect(200)
.end(function(err, res){
if (err) return done(err);
done()
});
...
// Router
app.get('/user', function(req, res){
res.render('user.jade');
});
The router works fine when I test it manually with the browser, and renders the jade template view but when I run the mocha tests it fails with 'Uncaught expected false to be true'. Express logs the request as successful (200), but the test still shows up as failed with the error message.
I have tried to use different code idioms for the SuperTest segment, different jade templates and it only seems to occur the first time I run a test on a router that renders a template. Following tests with GET even on the same router and template succeeeds???
Have tried to find code samples with routers that render a view without success, so perhaps this is an unsupported scenario?
var request = require('supertest');
var app = require('../server').app;
var jade = require('jade');
var should = require('should');
// ...
it('return user template', function(done){
request(app)
.get('/')
.end(function(err, res){
var html = jade.renderFile('./pathToJadeFile/user.jade');
res.text.should.equal(html);
done();
});
});
Related
I have an Express app contains two files on the root
(index.js + index.html)
and a folder named: server, contains a file [app.js] that listening to port 3000
I run index.html through Live Server on port: 5500
In [index.html + index.js] I fetch data through buttons from port 3000 where app.js is listening on, and it working good.
But my problem is when the user type a link in the frontend (localhost:5500/NotFound), in this case the browser won’t find the link and will get an error: Cannot Get…
What I want is to show a message “Not Found” for any invalid URL comes from the frontend
my question isn’t about 404 error itself but how to let the frontend handles the navigation in the backend.
my app structure is like this:
//app.js on port:3000/backend
app.get('/test', (req, res) => res.send('This is a test'))
app.get('/*', (req, res) => res.send('Not Found'))
//OR
app.get('/*', (req, res) => {
res.sendFile(__dirname, '/404.html');
})
app.listen(3000, () => console.log(`server up`))
//index.js on port 5500/frontend
document.addEventListener('DOMContentLoaded', function () {
fetch('http://localhost:3000/test)
.then(...)
})
now let’s say that it’s in the localhost:
frontend (index.html + index.js) are on http://127.0.0.1:5500/
backend (app.js) is on http://127.0.0.1:3000/
this working good if it requested from backend http://127.0.0.1:3000/ but if the request handled from the frontend (http://127.0.0.1:5500/NoExists ) it gets [Cannot GET /NoExists]
So, how to send a message {Not Found} to http://127.0.0.1:5500/NoExists?
in other words:
let app.js (backend) triggered by any request sent from index.js(frontend)
Use the response.status method
app.get('/*', (req, res) => res.status(404).send('Not Found'))
Edit: hopefully I understand the question now
You can leave the path blank to let it work for any undefined routes
app.get((req, res) => res.status(404).send('Not Found'));
// OR
app.get((req, res) => res.status(404).sendFile(__dirname, '/404.html'));
Edit 2: So from the video you sent, I think you want to display a 404 message in the front end.
Create a file in the same location as index.html and name it 404.html. This is just a normal html file, but its contents will be displayed when a page isn't found.
It looks like you're using live server, and I've found that the 404.html file doesn't work on live server, but it will work for most hosting services. Check this article or this one.
I'm using a MVC pattern, structuring everything in that style.
Server.js call a routes.js file which is a list of all possible routes. From there I want to call the controller functions to respond to that request.
I am getting getting a 404 error when trying to use a function from the user controller.
Server started on port 3000
MongoDB Connected...
GET /signUp 404 28.578 ms - 37
Server.js
//expose
module.exports = app;
// Bootstrap models //
fs.readdirSync(models)
.filter(file => ~file.search(/^[^.].*\.js$/))
.forEach(file => require(join(models, file)));
// Bootstrap routes //
require('./config/passport')(passport);
require('./config/express')(app, passport);
app.use('/', routes);
routes.js
const express = require('express');
const router = express.Router();
// Controllers //
const Users = require('../../app/controllers/users');
// Other routes //
const index = require('./landing');
//Access to routes
router.use('/', index);
// USER ROUTES //
router.post('/signUp', Users.signUp);
module.exports = router;
users.js
// #route POST signUp
// #desc SignUp/ Register user
// #access Public
const signUp = async (req, res) => {
[
//some checks
];
try{ //some code
} catch(err) {
//error handling
}
It seem that it's due to the controller function, I've tried changing the way I declare the route, but this is the way that makes the most sense to me.
I've started from this "boilerplate", but I don't get the way the routing works. So I changed it to be this way.
What am I doing wrong?
EDIT
As pointed out by #GavinGavin in the comments, the 404 error I'm getting does not match the router.post bit.
So I've went and tried with Postman, It seems to detected the route since I get a 500 server error now. However, in the browser I'm still getting a 404 request.
POST /signUp 500 60.652 ms - 73 //when I try with postman
GET /signUp 404 36.685 ms - 37 //when I refresh the browser
Introduction
So far I have three files, one test.js is a file where I have built three functions that work.
But now I am trying to structure using MVC or at least some pattern. So now I router.js and app.js
Question
Should I put my promise functions from test.js in my config.js or server.js or something else, Im just interested in how people would do this and whats the correct way of structuring NodeJS.
server.js
In here start the server and apply the routes to my app
var configure = require('./router');
var express = require('express');
var app = express();
var port = process.env.PORT || 8080;
// get an instance of router
var router = express.Router();
configure(router);
app.listen(port);
console.log('Server has started!! ' + port);
// apply the routes to our application
app.use('/', router);
config.js
In here I build my routes
module.exports = function (router) {
// route middleware that will happen on every request
router.use(function (req, res, next) {
// log each request to the console
console.log(req.method, req.url);
// continue doing what we were doing and go to the route
next();
});
// home page route (http://localhost:8080)
router.get('/', function (req, res) {
res.send('im the home page!');
});
// sample route with a route the way we're used to seeing it
router.get('/sample', function (req, res) {
res.send('this is a sample!');
});
// about page route (http://localhost:8080/about)
router.get('/about', function (req, res) {
res.send('im the about page!');
});
// route middleware to validate :name
router.param('name', function (req, res, next, name) {
// do validation on name here
console.log('doing name validations on ' + name);
// once validation is done save the new item in the req
req.name = name;
// go to the next thing
next();
});
// route with parameters (http://localhost:8080/hello/:name)
router.get('/hello/:name', function (req, res) {
res.send('hello ' + req.params.name + '!');
})
// app.route('/login')
// show the form (GET http://localhost:8080/login)
.get('/login', function (req, res) {
res.send('this is the login form');
})
// process the form (POST http://localhost:8080/login)
.post('/login', function (req, res) {
console.log('processing'); // shows on console when post is made
res.send('processing the login form!'); // output on postman
});
};
test.js
In here is a list of functions that are a chain of promises getting data and API Keys
(small function, one of many that feed into each over)
var firstFunction = function () {
return new Promise (function (resolve) {
setTimeout(function () {
app.post('/back-end/test', function (req, res) {
console.log(req.body);
var login = req.body.LoginEmail;
res.send(login);
resolve({
data_login_email: login
});
});
console.error("First done");
}, 2000);
});
};
My recommended structure is to put everything except server.js in lib directory so all your app is lib/ plus server.js - everything else is package.json, dependencies in node_modules (created on npm install, not in the repo), .gitignore, config files for Travis, Circle, Heroku or whatever service you're using, some README.md and things like that.
Now, server.js is just bare minimum that requires lib/app:
const app = require('./lib/app');
and starts the server with something like:
const server = app.listen(app.get('port'), () => {
logger.info('%s listening on port %s', app.get('name'), app.get('port'));
});
server.on('error', (err) => {
logger.error(err.message || err);
process.exit(1);
});
where logger is some higher lever logger like Winston or something like that.
That's it. Now, lib/app.js is minimum code that loads the middleware like body parsers etc., creates the express app and sets the variables for port and name and then uses a router that is exported by lib/routes:
const routes = require('./routes');
// ...
app.use('/', routes);
The lib/app should be enough to use for testing with tools like supertest but it doesn't listen on any port - server.js does. This is important to simplify testing.
The router exported by lib/routes is used for everything and you can start with a single lib/routes.js file and then convert it to lib/routes/index.js plus several files in lib/routes as needed.
The routes only define the actual routes and input validation with a module like e.g. express-validation and register controllers that are exported by lib/controllers - that can start as lib/controllers.js and get converted to lib/controllers/index.js plus lib/controllers/*.js as needed - just like the routes.
Then I would add top level spec or test or tests directory where all of the tests go. The tests can require your lib/app to run the tests on it with no need to listen on actual TCP ports - those will test your routes with actual controllers. Other tests will require lib/util and run some unit tests on your utilities etc. Make sure to use a tool like istanbul or nyc to calculate the test coverage.
The database schemas and data models would go to lib/schemas and lib/models, some utility helpers in lib/util, some config loading code in lib/config etc.
This is quite flexible layout and works pretty well. You can start with just few files:
README.md
LICENSE.md
package.json
server.js
lib/app.js
lib/routes.js
lib/controllers.js
lib/config.js
etc. and easily convert all of the xxx.js file into xxx/index.js with entire folder of smaller xxx/*.js files as needed.
The main difference from your approach is that I recommend exporting routers and using them by higher level routers instead of passing the high level router into lower lever modules that export functions that take routers to work on.
So instead of:
const moreSpecific = require('more-specific');
const moreGeneral = express.Router();
moreSpecific(moreGeneral);
and then in more specific:
module exports = (router) => {
router.use('/abc/xyz', ...);
};
I would recommend exporting a more specific router in a file e.g. routes/abc.js:
const router = express.Router();
router.use('/xyz', ...);
module exports = router;
and then in more general router e.g. in routes/index.js:
const abc = require('abc');
const router = express.Router();
router.use('/abc', abc);
// and export the main router for other modules like app.js to use:
module.exports = router;
to have a route like /abc/xyz.
I'm using Node and Anugular, and I have created a RESTful api from my application, and created an angular resource to use this. I'm confused as to how the Angular ui-router directive reconciles with the Node Routing system on the server.
At the moment I have set up my routes/states in ui-router like this:
$stateProvier
.state('admin', {
url:'/admin',
templateUrl:'views/admin.html',
controller: 'adminController'
});
And this loads into the ui-view on my homepage, when I navigate to this url from a link on the loaded page.
HOWEVER, when I manually type in localhost/admin I get the route from Node, rather than the state render through angular.
Now I'd like to Angular to handle all the navigation on my app, and my resource to get the information, even if the address is typed manually into the navigation bar.
I've created a route in Node is for index, which contains my index.html page from angular, which effectively contains the whole app angular code, including all the routing.
My question is, how can I get angular redirect if I manually type the url into the address bar, and still have the data from the $resource.
I'm directing my resource to '/admin' - could this be the problem?
Does this mean that I need to add the contents of /routes/appointments' into the base node file (server.js), and then remove the route? If so then how do i direct my resource to the correct REST api?
app structure
public
-angular app
-app.js //for angular
routes
index.js
appointments.js
models
views
- index.ejs
server.js //node server file
here is my code exerpts
server.js
//standard routing code
var routes = require('./routes/index');
var appointments = require('./routes/appointments');
var app = express();
//configuring Express
app.set('port', process.env.PORT || 3000);
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.set('views', __dirname + '/views');
app.engine('html', require('ejs').renderFile);
app.set('view engine', 'html');
app.use('/', routes);
app.use('/', appointments);
routes/index.js
var express = require('express');
var router = express.Router();
// ./routes/index.js
router.get('/', function(req, res) {
res.render('index', { title: 'Homepage' });
});
module.exports = router;
routes/appointments.js - this is the basis of my RESTFUL api
var express = require('express');
var router = express.Router();
var mongoose = require('mongoose');
var Todo = require('../models/Appointments.js');
/* GET /todos listing. */
router.get('/admin', function(req, res, next) {
Todo.find(function (err, todos) {
if (err) return next(err);
res.json(todos);
});
});
module.exports = router;
One way to do this is via the Accept header. If the request only accepts JSON then let the request go through to your API. If the request accepts HTML then always serve up your index page. Then once the index page loads angular's router will take care of the rest.
// Angular config - default Accept header for all ajax requests
$httpProvider.defaults.headers.common = {
'Accept': 'application/json'
};
// Middleware in Node to "intercept" non JSON requests
// Place this after express.static middleware but before your route definitions.
app.use(function(req, res, next) {
// keep in mind this applies to all requests, so 404s will need to be handled by your angular app since any url will serve up the index page
if(req.header('Accept') !== 'application/json') {
console.log('serving the index page');
req.url = '/'; // force the url to serve the index route.
}
next();
});
One more thing to note about this approach is that obviously you won't be able to see/debug your JSON data by hitting the URL directly anymore. There are several useful tools like Advanced REST Client or POSTman which actually give you better control and more options for things like that. Just make sure you set the Accept header in one of those tools and you'll be able to see the JSON response.
The actual URL is localhost#!/admin, try that. Angular hides the hashbang #!
Angular's URL routing is an "illusion" in that way. It only works on the client-side and only when your Angular app is loaded, which is on the main / route.
A solution could be to conditionally redirect from localhost/admin to localhost#!/admin, i.e. redirecting to your Angular app and passing it the #!/admin path. The condition could be a check for whether or not JSON was requested.
router.get('/admin', function(req, res, next) {
if(req.header('Accept') !== 'application/json')
return res.redirect('/#!/admin');
Todo.find(function (err, todos) {
if (err) return next(err);
res.json(todos);
});
});
You'll also need to configure Angular such that when it requests '/admin' json data from the server, it should only accept json (by setting the request header), which is how the the server will distinguish it from the regular '/admin' request. For that, if you're using $http.get you would do $http.get('/admin', {'Accept':'application/json'})
I have an app where I am trying to remove the hashbang ( ! and #) prefixes for my routes, but still have people be able to use bookmarked routes. For the most part I have been able to get it work with html5Mode set to true, but there are a few cases where it is not working. Here is how I have my server configured:
var router = require('./router')(app);
app.use(express.static(path.join(__dirname, '../client')));
app.get('*', function (req, res, next) {
res.sendFile('index.html', {root:'../client/app/'});
});
router in this case looks like this:
var express = require('express');
var router = express.Router();
var Products = require('../../database').Products;
router.get('/:flavor', function (req, res) {
var flavor = req.params.flavor;
Products.findOne({flavor:flavor}, function (err, product) {
if (err) {
throw err
}
res.json(product);
});
Getting the flavor routes, is one case where this setup does not work. If someone directly types into the browse, mysite.com/lemon they receive the JSON data back, only (no template or anything). Normally this is used by the angular app, (which would typically make the request to /lemon and implement it into the template). However, if I move the router below the app.get('*'), then any request made by Angular for the data is returned with the entire index.html page. How can I make it so that a direct request by the browser for a route that normally returns JSON sends the index file?