0

I need to define path for the APIs I support in my Node.js express application.
I am facing one issue related to receiving URL variables.

// app.js
const bodyParser = require('body-parser');
const express = require('express');
const addressRouter = require('./routes/address-router');
const http = require('http');

const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.set('port', 8081);

app.use((req, res, next) => {
  res.status(404).send({
    error: 'path not found',
  });
});

app.use('/v1/users/:userId/zip/:zipNumber/address', addressRouter);

const server = http.createServer(app);
server.on('error', onError);
server.on('listening', onListening);
server.listen(8081);
// addressRouter.js
const express = require('express');
const router = express.Router();

router.get('/zip-get', async (req, res, next) => {
  console.log('GET: /zip-get: req: ', req);
  res.send('Done');
});

module.exports = router;

The API I am calling:
curl http://localhost:8081/v1/users/123-456/zip/1234567/address/zip-get

In the addressRouter.js when I print the received req I get:

params: {}

and

baseUrl: '/v1/users/:userId/zip/:zip/address',
originalUrl: '/v1/users/:userId/zip/:zip/address/zip-get',

Update
In the request object I see below fields: Does that indicate incoming request is not yet ended?
ReadableState {
objectMode: false, highWaterMark: 16384, buffer: BufferList { head: null, tail: null, length: 0 }, length: 0, pipes: [], flowing: null, ended: false, endEmitted: false, reading: false, sync: true, needReadable: false, emittedReadable: false, readableListening: false, resumeScheduled: false, errorEmitted: false, emitClose: true, autoDestroy: false, destroyed: false, errored: null, closed: false, closeEmitted: false, defaultEncoding: 'utf8', awaitDrainWriters: null, multiAwaitDrain: false, readingMore: true, dataEmitted: false, decoder: null, encoding: null, [Symbol(kPaused)]: null
}

I am not sure what can cause this.

6
  • If the question is about addressRouter, show its code. No way to tell whether you simply have a typo, called the wrong function, or wrote something wild and crazy that could never work in the first place =) Commented Apr 1, 2023 at 3:56
  • Please refer the updated question. I did also try to move the path next to /v1 to the route file but still same issue. Commented Apr 1, 2023 at 4:00
  • I don't see your code accessing req.params? Commented Apr 1, 2023 at 4:03
  • @Mike'Pomax'Kamermans If you see the line console.log('GET: /zip-get: req: ', req); in the file addressRouter.js I am logging the req object and in the logs I could see params: {} is empty, accessing it will result in {}. To check the value of the req.params I don't need to access it and print empty object, I can clearly make out from the logs that it is empty. I hope it is clear. Commented Apr 3, 2023 at 5:39
  • Don't tell "me", tell "everyone" by putting that information in your post instead of in a comment. Also, note that there is no need for http if you're using express: your app already has app.listen() built in. Commented Apr 3, 2023 at 14:46

2 Answers 2

1

A few problems here: as per the official express documentation for how to handle 404's, you need to move that 404 handler. Express evaluates routes "top down", so your 404 handler is currently kicking in for everything.

Also, no need for the http module, express is a web server, it already gives you app.listen to start the server.

But the most import thing is that routers are their own thing. By using them, you are creating a routing context that is isolated from the rest of the app, which includes having its own params object that will only contain the parameters defined by the router itself, so because your /zip-get route has no params, the req.params object will be empty inside the router's middleware chain.

However, inside the app.use middleware chain, which has a path that does have parameters in it, req.params will have all the values you'd expect, so: copy the values you need from req.params into res.locals as part of your app.use middleware chain, so that any downstream middleware (which includes downstream routers) has access to them.

import express from "express";
const app = express();
const router = express.Router();

router.get('/zip-get', (req, res) => {
  console.log(`zip-get stored values:`, res.locals);
  res.send('Done');
});

function copyParams(req, res, next) {
  res.locals = {...req.params};
  console.log(`app level params:`, req.params);
  next();
}

app.use('/v1/users/:userId/zip/:zipNumber/address', copyParams, router);

// This has to come last.
app.use((req, res, next) => {
  res.status(404).json({
    error: 'path not found',
  });
});

app.listen(8081, () => console.log(` http://localhost:8081`));

Alternatively, you can tell the router that it should preserve params when you call new Router by passing mergeParams: true as an option, but then you need to ask yourself why you're using a router at all: express is based on the concept of using a middleware chain to do "individual bits of work, based on the current context", so any code that relies on turning :userId and :zipNumber into real things should be kicking in before you hit the router middleware, with the intermediary data stored in res.locals. The way you've written things, /zip-get should not be doing anything with those parameters, because it is not responsible for them.

Instead, organize your API by what it needs to do, e.g. have a truly minimal main file:

import express from "express";
import userRoutes from "./routes/user.js";

const app = express();

app.use('/v1/users', userRoutes);

app.use((_req, res, _next) => {
  res.status(404).json({
    error: 'path not found',
  });
});

app.listen(8081, () => console.log(` http://localhost:8081`));

With the user routes being responsible for users, and only users:

import express from "express";
import db from "./somewhere-or-other.js";
import zipRoutes from "./zip.js";
...

function findUser(req, res, next) {
  db.getUserById(req.params.userId, (err, user) => {
    if (errr) {
      return next(err);
    }
    res.locals.user = user;
    next();
  });
}

const userRoutes = express.Router();
userRoutes.use('/:userId/zip', findUser, zipRoutes);
userRoutes.use('/:userId/something-else', findUser, someOtherRoutes);
export default userRoutes;

With a separate zip routes handler:

import express from "express";
import db from "./somewhere-or-other.js";
...

function getZipObject(req, res, next) {
  zipManager.getZipObject(req.params.zipNumber, (err, zipData) => {
    if (errr) {
      return next(err);
    }
    res.locals.zip = zipData;
    next();
  });
}

const zipRoutes = express.Router();
zipRoutes.get('/:zipNumber/address/zip-get', getZipObject, (req, res) => {
  // Render the page for this route, with res.locals as its context
  // so that the page template can access the user and zip data.
  res.render(`zip-get.html`, res.locals);
});
export default zipRoutes;

And note that we're terminating in a function that does nothing other than render the appropriate template as page response. All the work that needed to happen should, by the time we hit the final response function, have already been done and turned into res.local data that can be passed straight into the template rendering call.

Sign up to request clarification or add additional context in comments.

6 Comments

Thanks for the answer, but after doing these changes I am still not able to receive parameters. May be problem is else where.
It must be, because the first block of code is fully self-contained (barring the imports), and can be run by copy-pasting it (although I used ESM rather than CJS, mostly because ESM is the "official" way to do imports and exports, whereas CJS is a legacy format that Node invented before ESM existed).
In the incoming request in the object ReadableState I see fields flowing: null, ended: false, endEmitted: false does these fields indicate request is not complete yet?
Instead of trying to keep digging through your code, just take the code I've given you here, which we know works, and build your own code back out based on it.
With the code you provided I see the same behavior.
|
0

Yes you can move the path /users/:userId/zip/:zip/ up your addressRouter.js router's path as you have asked. That's doable since by default each router has access to the req.params that are defined within its own path.

Another option is to use the optional object to the express.Router() function call, that has a property of mergeParams and pass it a value of true - which would allow you to preserve the req.params values from the parent router.

Modify your addressRouter.js into

const express = require('express');
// Here pass in the mergeParams property set to true
const router = express.Router({ mergeParams: true });

router.get('/zip-get', async (req, res, next) => {
  console.log('GET: /zip-get: req: ', req);
  res.send('Done');
});

module.exports = router;

1 Comment

Thanks for the answer, this did not work for me.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.