I'm using express 4.16.3 and trying to make sense of why one request to a controller works and a request doesn't.
in my server.js i've got the following. There's no semi-colons because used prettier beforehand.
import express from 'express'
import bodyParser from 'body-parser'
import cors from 'cors'
import PriceCheckerController from './controllers/PriceChecker'
import PersonalLibraryController from './controllers/PersonalLibrary'
const app = express()
app.set('port', process.env.PORT || 5000)
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({extended: true}))
app.use(cors())
// routes definition
app.use('/api/books',PersonalLibraryController) // this does not
app.use('/api/stock-prices', PriceCheckerController) // this works
//
app.listen(app.get('port'), error => {
if (error) {
logger.error(`error fcc-isqa:${error}`)
} else {
logger.info(`fcc-isqa is running on port ${app.get('port')}`)
}
})
export default app
In PriceCheckerController i've implemented it like so.
import 'babel-polyfill' // mocha required
import express from 'express'
import logger from '../logger'
const PriceCheckerController = express.Router()
PriceCheckerController.use((req, res, next) => {
logger.info(
`date=>${new Date()}\n method=>${req.method}nsender:${req.ip}`
)
})
PriceCheckerController.get('/', async (req, res) => {
return res.status(200).json({message:'soon'})
})
export default PriceCheckerController
In PersonalLibraryController i've implemented it like so
import 'babel-polyfill'
import express from 'express'
import logger from '../logger'
const PersonalLibraryController = express.Router()
PersonalLibraryController.use((req,res,next)=>{
logger.info(
`library date=>${
new Date()}method=>${req.method}url=>${req.baseUrl}${req.path}`
)
})
PersonalLibraryController.route('/test')
.get(async (req, res) => {
return res.status(200).json({message: 'get soon'})
})
.post(async (req,res)=>{
return res.status(200).json({message: 'post soon'})
})
export default PersonalLibraryController
A request to /api/stock-prices returns ok with message soon.
A request to /api/books/test is logged by the middleware but a response is not sent back. It eventually gives a timeout
Can anyone give me any insights/ help in understanding what is the problem and how to fix it?
Thanks in advance.
Your middleware functions need to call next() to carry on the route execution.
e.g
PriceCheckerController.use((req, res, next) => {
logger.info(
`date=>${new Date()}\n method=>${req.method}nsender:${req.ip}`
)
next();
})
https://expressjs.com/en/guide/using-middleware.html
"If the current middleware function does not end the request-response cycle, it must call next() to pass control to the next middleware function. Otherwise, the request will be left hanging."
Related
I have a Nodejs with express application. I register my routes using tsoa.
When i add swagger-ui-express to my nodejs application I get the following error Error: TypeError: Router.use() requires a middleware function but got a undefined
I initialize the app as follows:
app.ts
import config from './api/build/config';
import express from 'express';
function startServer() {
const app = express();
require('./api/loaders').default(app);
app.listen(config.port, () => {
console.log(`
################################################
🛡️ Server listening on port: ${config.port} 🛡️
################################################
`);
});
}
startServer();
loaders\index.ts
import {Express} from 'express';
export default (app: Express) => {
require('./express').default(app);
console.log('✌️ Express loaded');
require('./swagger').default(app);
console.log('✌️ Swagger loaded');
};
express.ts
import bodyParser from 'body-parser';
import {Express, Request, Response, NextFunction} from 'express';
import logger from 'morgan';
import { RegisterRoutes } from '../routes';
import cors from 'cors';
export default function startExpress(app: Express) {
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(cors());
//register all routes from the routes generated by tsoa
RegisterRoutes(app);
// catch 404 and forward to error handler
app.use((request: Request, response: Response, next: NextFunction) => {
const error = new Error('404 Not Found');
error['status'] = 404;
next(error);
});
// error handlers
// error handler will print stacktrace only in development
app.use((error: any, request: Request, response: Response) => {
response.locals.message = error.message;
response.locals.error = request.app.get('env') === 'development' ? error : {};
response.status(error.status || 500);
response.send(error.message);
});
}
swagger.ts
import { Express } from 'express';
import swaggerUi from 'swagger-ui-express';
export default function startSwagger(app: Express) {
try{
const swaggerDocument = require('../build/swagger.json');
var options = {
explorer: true
};
app.use('/swagger', swaggerUi.server, swaggerUi.setup(swaggerDocument, options));
}
catch(error){
throw new Error(error);
}
}
I also tried to use import statements instead of require, but it doesn't make a difference. Why does my compiler suddenly say my app Express object is a Router object and how do I set up nodejs with express and registering routes in different files?
To answer your question...
Why does my compiler suddenly say my app Express object is a Router object...
It doesn't. You can see a reference to the Router.use() function because it is just eventually called inside the app.use() function.
The actual issue here as mentioned in the error message is the middleware function being undefined. This is because inside your swagger.ts file, you specified swaggerUi.server as the middleware function but it needs to be changed to swaggerUi.serve.
import { Express } from 'express';
import swaggerUi from 'swagger-ui-express';
export default function startSwagger(app: Express) {
try{
const swaggerDocument = require('../build/swagger.json');
var options = {
explorer: true
};
// The problem was here... change server to serve
app.use('/swagger', swaggerUi.serve, swaggerUi.setup(swaggerDocument, options));
}
catch(error){
throw new Error(error);
}
}
Using the Following Stack Express,Vue,SQL,Axios
GET request is working fine in postman as well from Axios
POST request created error in both attached screenshots
To make sure the Backend is working fine I have tried sending the Data directly from
<form action="url" method="POST">
it is Working fine and data is storing in the database
I have Tried few workaround like disabling SSL setting in postman and Played with proxy setting Also having CORS enabled in the backend and tried some Allow content and header things. Nothing worked
Not able to figure out the Problem in the POST Request. Please Help
--Request Error in the browser from Axios ----
Axios Browser Error
-postman Error when doing POST Request---
Postman Error
---Backend Index.js file---
// const express = require("express");
"use strict";
import express from "express";
const app = express();
import cors from "cors";
//listening on this port
app.listen(3000);
app.use(cors()); // to get Data from Other Domains
app.get("/", function (req, res) {
res.send("Application Started");
console.log("Application Started");
});
//Routes
app.use("/product", require("./routes/product"));
---product.js routes files---
import express from "express";
const router = express.Router();
import bodyParser from "body-parser";
//Middleware
router.use(bodyParser.urlencoded({ extended: true })); // To Parse the body data
//Importing Main Controller
import conProduct from "../controllers/ConProduct";
//Defining functions as per Routes
router.post("/add", conProduct.add); //Showing all the products
router.get("/get", conProduct.get); //Showing all the products
//Exporting Router
module.exports = router;
---Controller for Product file ConProducts.js ---
import sqlConfig from "../database/dbConfig";
let sql = sqlConfig.mysql_pool;
exports.add = (req, res) => {
console.log(req.body);
const { name, shortDescription, price, discount } = req.body;
let addQuery =
"INSERT INTO products(name,short_description,price,discount) VALUES('" +
name +
"','" +
shortDescription +
"','" +
price +
"','" +
discount +
"');";
sql.query(addQuery, (err, result) => {
if (err) throw err;
console.log(result);
res.send("product uploaded");
});
};
--Frontend axios Request --
let formData = {
name: this.name,
shortDescription: this.shortDescription,
price: this.price,
discount: this.discount,
};
console.log(formData);
axios
.post("/product/add", formData)
.then((res) => console.log(res))
.catch((error) => console.log(error));
I got the Answer I was missing a middleware app.use(bodyParser.json) in index.js and because of that there was no value going into database and thus, there was a network error.
I realized that, you are sending a PUT request to backend but your API controller receiving POST requests. So, Don't they have to be same type of request ?
I know my problem may seem not very specific, but I'm having problem describing what's happening because I don't understand it :(
So I've written small express app with ssr (for react) and jwt authentication. The SSR part works nice but the rest is crap.....
import 'babel-polyfill';
import express from 'express';
import bodyParser from 'body-parser';
import logger from 'morgan';
import authRouter from './auth/authRouter';
const app = express();
app.use(express.static('public'));
app.use(logger('dev'));
app.use(bodyParser.json({ type: '*/*' }));
app.use(bodyParser.urlencoded({ extended: false }));
//a lot of irrelevant ssr code
authRouter(app);
app.listen(3000, function(err) {
if (err) {
console.log(err);
}
console.log('main server on port 3000');
});
This is my main server file. My first problem is I don't see ANY console.logs from my files. There's nothing in my terminal. That's the reason I can't see how does my requests look like in my app. I'm testing it using postman like that:
And that's the authRouter I'm using above in main server file:
import express from 'express';
import { signup, signin } from '../controllers/authentication';
import { jwtLogin, localLogin } from '../services/passport';
import passport from 'passport';
passport.use(jwtLogin);
passport.use(localLogin);
//if user is auth'd do not create session for him
const requireAuth = passport.authenticate('jwt', { session: false });
const requireSignin = passport.authenticate('local', { session: false });
const authRouter = function(app) {
app.get('/auth', requireAuth, function(req, res) {
res.send({ hi: 'there' });
});
app.post('/auth/signin', requireSignin, signin); // irrelevant right now
app.post('/auth/signup', signup);
};
export default authRouter;
And that's signup function I'm using in router:
const signup = (req, res, next) => {
console.log('reqqqqqqq', req);
const { email, password, passwordCheck } = req.body; //code crashes here
//some code I deleted for everyones convenience
};
Every request I make my app crashes because req.body is undefined. I can't log req because I can't see any logs. I also tried sending back stringified version of my request body but every time i get "TypeError: Converting circular structure to JSON".
I'll be happy to add any information you may need
EDIT:
I'm gonna check that later at home but now I'm thinking there is something wrong with ssr part of my app because I don't even see that 'main server on port 3000' log..... At the same time server responds with right html, js files and routing works well so.....anyway I'm gonna look it up later
Try using util.inspect from node:
const util = require('util');
// usage in your code
const signup = (req, res, next) => {
console.log(util.inspect(req, { showHidden: true, depth: null }));
const { email, password, passwordCheck } = req.body;
...
};
I solved it.....unexpectedly the problem was lying in my package.json file.
My start script looked like this:
"scripts": {
"start": "nodemon build/server.bundle.js | nodemon build/api.bundle.js",
...
}
And because of that I had these weird errors. Only api.bundle.js file was running correctly......
Anyway thanks for your help ;)
I'm running a standard NodeJs 8 with Express and currently when a request for an existing path but un-supported method comes in, Express return 404.
For example 'POST /login' is supported, but 'GET /login' is not, but it returns 404.
How can I make Express return 405 in such a case?
Here's the routes file:
const express = require('express');
const router = express.Router();
const loginController = require('../controllers/login');
router.route('/login').post(loginController.loginUser);
module.exports = router;
Please advise.
You can simply add the .all() handler to your route chain, like so:
const methodNotAllowed = (req, res, next) => res.status(405).send();
router
.route(`/login`)
.post(loginController.loginUser)
.all(methodNotAllowed);
Explanation
This works because requests are passed to the handlers in the order they are attached to the route (the request "waterfall"). The .post() handler will catch your POST requests, and the rest will fall through to the .all() handler.
Also see this question for more details.
Authenticating all POST routes
If you would like to ensure that the user is logged in for all POST requests, but return a 405 response for any other requests, you can use a regular expression to match all routes with router.post('*'), like so:
router
.post(`*`, loginController.loginUser)
.all(methodNotAllowed);
The problem with this approach, however, is that no 404 errors will ever be returned to the client, only 405. Therefore I recommend attaching the methodNotAllowed handler to each individual route, like in the first code snippet above. This approach will return 404 errors for routes that don't exist, but 405 errors for routes that do.
Determining the available methods for a route
To determine which methods are allowed for a route, use router.stack:
app.use((req, res, next) => {
const methods = router.stack
// Filter for the route that matches the currently matched route
.filter(layer => layer.route.path === req.path)[0]
.route
.methods;
if (!methods[req.method]) methodNotAllowed(req, res, next);
else next();
});
You can try this that way:
app.route("/login")
.get((req, res) => {
/* HANDLE GET */
})
.post((req, res) => {
/* HANDLE POST */
})
.all((req, res) => {
res.status(405).send();
});
How it works?
If request matches the route. It will go through the handlers. If a handler is present, it will be handled using that specific one. Otherwise, it will reach the 'all' handler that will set the status code to 405 and send the response.
Here You can find the discussion about it:
405 issue
#You question below:
You can try that way:
loginRoutes.js content:
const router = require('express').Router();
router.route('/')
.get((req, res) => {
res.status(200).send()
})
module.exports = router
server file content:
const express = require('express')
const app = express();
const router = express.Router();
const loginRoutes = require('./loginRoutes')
const PORT = process.env.PORT || 8080;
router.use('/login', loginRoutes)
router.route('/login').all((req, res) => { res.status(405).send() })
app.use(router);
app.listen(PORT, () => console.log(`started on port: ${PORT}`))
You can use this snippet of code to automatically send 405 status code when route from the same path exist but not with the current method
app.use(function (req, res, next) {
const AllLayers = app._router.stack
const Layers = AllLayers.filter(x => x.name === 'bound dispatch' && x.regexp.test(req.path))
const Methods = [];
Layers.forEach(layer => {
for (let method in layer.route.methods) {
if (layer.route.methods[method] === true) {
Methods.push(method.toUpperCase());
}
}
})
if (Layers.length !== 0 && !Methods.includes(req.method)) {
res.setHeader('Allow', Methods.join(','))
if (req.method === "OPTIONS") {
return res.send(Methods.join(', '))
}
else {
return res.sendStatus(405);
}
}
else {
next();
}
});
Hope this could be helpfull to someone
If you want to determine what methods COULD have been used you need to do a lot of digging in the app function you start your server with, and through some string manipulation and the like you can figure out what the possible methods are and return them in the error. If you're interested in how its done check out https://github.com/Justinlkirk/express-ez-405 or just use the npm package here https://www.npmjs.com/package/express-ez-405
I try to get the :userId "albert" from this url
http://localhost:5000/search/albert?query=al&page=1
at server side but failed, what can I do to get the react-router defined params correctly at node.js with express?
routes.js
[
{
path: '/search/:userId',
component: Search,
}, {
path: '/search',
component: Search,
}
...
]
server.js
server.get('*', async (req, res, next) => {
const pageData = await routes
.filter(route => matchPath(req.path, route))
.map((route) => {
console.log(route)
return route.component
})
}
The React-Router Way
React Router V4 does include a way to extract param data server-side using their matchPath() function, using their standard parameter implementation, "/path-name/:param" route matching.
In this case, it allows me to do a lot of server-side stuff based on the parameter before the express app responds with the page data.
NOTE: this is probably not the most basic implementation, but it's a pared down version of my complete SSR react implementation that makes use of matchPath().
Requirements
Server-side rendered react app
React-router-dom v4
Centralized routes file (because SSR)
Express app server (I'm hosting my express app on Firebase)
In This Example, a server-side express app attempts to run an "initialAction" function in each component during a fresh page load. It passes promise resolve and reject to know when the function is completed running, and the request object which may contain useful params we can extract with matchPath(). It does this for every matching route, again, using matchPath().
Routes.js Example
Where :id is the "id" param in the URL.
const routes = [
{
path: "/news-feed/:id",
component: NewsFeed,
exact: true
},
]
export default routes;
Component Example
Just showing the initialAction() function in the component
import { Link, matchPath } from 'react-router-dom';
class NewsFeed extends Component {
// Server always passes ability to resolve, reject in the initial action
// for async data requirements. req object always passed from express to
// the initial action.
static initialAction(resolve, reject, req) {
function getRouteData() {
let matchingRoute = routes.find(route => {
return matchPath(req.path, route);
});
console.log("Matching Route: ", matchingRoute);
return matchPath(req.path, matchingRoute);
}
let routeData = getRouteData();
console.log("Route Data: ", routeData);
}
/** REST OF COMPONENT **/
Console.log output for the url www.example.com/news-feed/test would be
Route Data: { path: '/news-feed/:id',
url: '/news-feed/test',
isExact: true,
params: { id: 'test' } }
As you can see, we've found our param on the server-side using no regex. matchPath() did the work for us. We can use nice, clean urls.
Server-side index.js
Where the initial action is called, with the promise resolve, reject, and req objects. Keep in mind this is a firebase hosting example and may differ for different hosting providers - your method for the initialAction function call may also differ.
import React from "react";
import ReactDOMServer from 'react-dom/server';
import { Provider } from "react-redux";
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import { StaticRouter, matchPath } from "react-router-dom";
import routes from "../shared/components/App/routes.js";
import express from "express";
import * as functions from "firebase-functions";
// Import Components, Reducers, Styles
import App from "../shared/components/App";
import reducers from "../shared/reducers";
// Prepare our store to be enhanced with middleware
const middleware = [thunk];
const createStoreWithMiddleware = applyMiddleware(...middleware)(createStore);
// Create store, compatible with REDUX_DEVTOOLS (chrome extension)
const store = createStoreWithMiddleware(reducers);
// Implement cors middleware to allow cross-origin
const cors = require('cors')({ origin: true });
const app = express();
app.get('**', (req, res) => {
cors(req, res, () => {
// Finds the component for the given route, runs the "initial action" on the component
// The initialAction is a function on all server-side renderable components that must retrieve data before sending the http response
// Initial action always requires (resolve, reject, req), and returns a promise.
const promises = routes.reduce((acc, route) => {
if (matchPath(req.url, route) && route.component && route.component.initialAction) {
acc.push(new Promise(function (resolve, reject) {
// console.log("Calling initial action...");
store.dispatch(route.component.initialAction(resolve, reject, req));
}));
}
return acc;
}, []);
// Send our response only once all promises (from all components included in the route) have resolved
Promise.all(promises)
.then(() => {
const context = {};
const html = ReactDOMServer.renderToString(
<Provider store={store}>
<StaticRouter location={req.url} context={context}>
<App />
</StaticRouter>
</Provider>
);
const preloadedState = store.getState();
res.status(200).send(renderFullPage(html, preloadedState));
})
.catch(function (error) {
console.log("Promise error at server", error);
});
});
});
module.exports = functions.https.onRequest(app);
Just used a sample node.js app to make a server.js which could be like
const express = require('express')
const app = express()
app.get('/search/:userid', (req, res) => res.json({ key: `Hello World for search with id=${req.params.userid}` }))
app.get('/search', (req, res) => res.send('Hello World!i for search'))
app.get('*', (req, res) => res.send('Hello World!'))
app.listen(3000, () => console.log('Example app listening on port 3000!'))
For the page number and other url params you can do like
req.query['page']
to retrieve the parameters.