How to organize express endpoints properly - node.js

I am having trouble wrapping my head around how to use the /:variable notation effectively. In my setup I have this layout.
router.route("/").get()
router.route("/:id").get().put().post().delete()
router.route("/auth").get().put().post()
When I call /auth it fails to go there, instead it triggers the method under the /:id. How can I make sure when I say /auth it does not goto that /:id path.

You need to adjust the order of route definition. Move /auth route to /:id before route.
E.g.
import express, { Router } from 'express';
const app = express();
const port = 3000;
const router = Router();
router.route('/auth').get((req, res) => {
res.send('auth');
});
router.route('/:id').get((req, res) => {
res.send({ id: req.params.id });
});
app.use('/user', router);
app.listen(port, () => console.log(`HTTP server is listening on http://localhost:${port}`));
Test and output:
⚡ curl http://localhost:3000/user/auth
auth%
⚡ curl http://localhost:3000/user/123
{"id":"123"}%

Related

How do I get the full route path including the parameters from express or extend the request object to do so?

I have the following route in my express (version 4.17.1) API in a postTimecardCompany.js file:
const mongoose = require('mongoose');
const Timecard = require('./../models/timecard');
function postTimecardCompany(server) {
server.post('/api/v1/timecard/:userId', (req, res) => {
// Insert timecard data into the database
Timecard.create(data, (error, result) => {
// Check for errors
if (error) {
res.status(500).end();
return;
}
// Respond
res.status(200).send({timecardId: result._id});
});
});
}
module.exports = postTimecardCompany;
The route (among other routes) is loaded via the following mechanism by server.js file:
[
'postTimecardCompany',
'anotherRoute',
'someOtherRoute',
'andSoOn...'
].map((route) => {
require('./core/routes/' + route + '.js').call(null, server)
});
I have a middleware (in server.js file) where I check which route is being called.
server.use((req, res, next) => {
// If route is "/api/v1/timecard/:userId" do something
});
I have found various solutions which do nearly what I am looking for, but not exactly.
For example, if I post to the route with a data parameter userId value of "123f9b" then req.originalUrl gives an output of "/api/v1/timecard/123f9b."
What i'm looking to get is the original route path with the parameters in it so for a request of "/api/v1/timecard/123f9b" it would be: "/api/v1/timecard/:userId."
How do I get this functionality in express or extend express to get the original route path with parameters in the request object?
if you want to use from your approach, it's is impossible, after that your approach is not standard in express check the documentation, if you want get routes in a middleware you should try like this:
server.js
const express = require('express')
const server = express()
const postTimecardCompany = require('./routes/postTimecardCompany.js')// don't use map()
server.use("/",postTimecardCompany)//use the routes
server.listen(6565,()=>console.log(`Listening to PORT 6565`))
routes of postTimecardCompany.js
use Router of express and export router, and you can use middleware before each route you want, there are many ways to use middleware in routes, check the documentation
const express = require("express");
const router = express.Router();
const middleware = require('../middleware');//require middlewares
router.post("/api/v1/timecard/:userId", middleware,(req, res) => {
// Insert timecard data into the database
console.log(req.route.path);
});
module.exports = router;
middleware.js
module.exports = ((req, res, next) => {
console.log(req.route.path);
next()
});

CORS anywhere returning proxy text, not desired target resource

I am trying to set up a proxy using node, express, and an instance of cors-anywhere for my arcgis-js-api app. My server file looks like this:
import express from 'express';
import cors from 'cors';
import corsAnywhere from 'cors-anywhere';
const { PORT } = process.env;
const port = PORT || 3030;
var app = express();
let proxy = corsAnywhere.createServer({
originWhitelist: [], // Allow all origins
requireHeaders: [], // Do not require any headers.
removeHeaders: [], // Do not remove any headers.
});
app.use(cors());
app.get('/proxy/:proxyUrl*', (req, res) => {
req.url = req.url.replace('/proxy/', '/');
proxy.emit('request', req, res);
});
app.get('*', (req, res) => {
res.sendFile(path.resolve(__dirname, 'index.html'));
});
app.listen(port, () => {
console.log(`App listening on port ${port}`);
});
When I go to http://localhost:3030/proxy/https://maps.disasters.nasa.gov/ags04/rest/services/ca_fires_202008/sentinel2/MapServer?f=json, I get to my target json no problem, with access-control-allow-origin: * correctly tacked on.
In my front end html (an arcgis-js-api app), I am calling that same url:
var layer = new MapImageLayer({
url: 'http://localhost:3030/proxy/https://maps.disasters.nasa.gov/ags04/rest/services/ca_fires_202008/sentinel2/MapServer?f=json',
});
My network tab shows a response not of the expected JSON, but of the text of the cors-anywhere proxy:
For those familiar with the arcgis-js-api, you can also preconfigure use of a proxy:
urlUtils.addProxyRule({
urlPrefix: 'maps.disasters.nasa.gov',
proxyUrl: 'http://localhost:3030/proxy/',
});
If I do it this way, the network tab shows that the call to the localhost:3030/proxy/<url> is returning the index.html page, not the desired json.
Why is the proxy giving the expected/required result when I access the url directly through the browser, but not when being called from my front end file? Thanks for reading.
I checked the browser console and noticed that the url being sent to the proxy instead of this
http://localhost:3030/proxy/https://maps.disasters.nasa.gov/ags04/rest/services/ca_fires_202008/sentinel2/MapServer?f=json
looks like this
http://localhost:3030/proxy/https:/maps.disasters.nasa.gov/ags04/rest/services/ca_fires_202008/sentinel2/MapServer?f=json
Not sure why it's happening, but as a quick fix you can replace
req.url = req.url.replace('/proxy/', '/');
with
req.url = req.url.replace('/proxy/https:/', '/https://');

How to attach a router to an express app based on a path specified by a regular expression?

With this very simple app:
const express = require('express');
const app = express();
const router = express.Router();
const port = 8080;
router.get('/test', (req, res) => {
res.send('Test was hit')
})
// binds router to express app
app.use('/root', router);
app.listen(port, () => logger.info(`Listening on port: ${port}`));
After running curl http://localhost:8080/root/test, unsurprisingly, the response is Test was hit
I am to make this much more generic and wish for the consumer to be able to hit curl http://localhost:8080/<whatever-the-consumer-specifies>/test and still hit the routers /test endpoint.
However, if I replace the binding to be as follows:
app.use('/*',router);
After a subsequent hit the response is Cannot GET /root/test
How can I instead achieve this?
* EDIT: *
Of course I could do:
router.get('*/test', (req, res) => {
res.send('Test was hit')
})
However - This is not the answer that I seek, I only want this configuration take place once in the project.
Express allows the use of regular expression in the router middleware as mentioned in the docs here.
app.use(/\/\w+/, router);
You could replace the string and place a regex in the first argument.

Nodejs Express - return 405 for un-supported method

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

NodeJS + React + Next Framework adding a path to the route

I'm attempting to setup a NodeJS application that is using the Next framework to utilize client and server side rendering. I'm trying to get the client and server side rendering to prepend a path to the routes/URLs it generates. The server side render seems to be working by setting up the express server GET function to listen for requests made on route and then passing that along to node by stripping out the prepended route value. However when it comes the rendering on the client the prepended value is missing even when the as="{somestring}" is added to the .js pages for elements like Link so when the external Next javascript files are referenced in the render it's missing the prepended value.
The purpose for the routing is to allow us to run multiple micro-services on one domain each hosted on different instances in AWS and being routed using Target Groups and an ALB.
Essentially what I want to do is replace / with /{somestring} and I need this to be included not only in the server side rendering but in the client side rendering.
URL Example:
www.example.com -> www.example.com/somestring
HTML Render:
www.example.com/_next/960d7341-7e35-4ea7-baf6-c2e7d457f0db/page/_app.js -> www.example.com/somestring/_next/960d7341-7e35-4ea7-baf6-c2e7d457f0db/page/_app.js
Edit/Update
I've tried to use app.setAssetPrefix and while it renders the requests for the assets correctly and the pages load the assets themselves are 404ing.
Here is my server.js file:
const express = require('express');
const next = require('next');
const port = process.env.PORT || 3000;
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();
app
.prepare()
.then(() => {
// Port
const server = express();
app.setAssetPrefix('test1');
// ======
// Routes
// ======
server.get('/test1/:id', (req, res) => {
const actualPage = `/${req.params.id}`;
const queryParams = { id: req.params.id };
app.render(req, res, actualPage, queryParams);
});
server.get('/test1', (req, res) => {
app.render(req, res, '/');
});
server.get('*', (req, res) => {
handle(req, res);
});
// =============
// End of Routes
// =============
server.listen(port, err => {
if (err) throw err;
console.log(`>Listening on PORT: ${port}`);
});
})
.catch(ex => {
console.error(ex.stack);
process.exit(1);
});
You need custom routing. Parse the incoming url and replace it with what you want.
Here is is an example to make /a resolve to /b, and /b to /a
https://github.com/zeit/next.js#custom-server-and-routing

Resources