I'm trying to figure out how to do something a bit unusual: basically I have a server that's both a combination Web frontend with client-side routing and a JSON-based API with server-side routing, but here's the tricky part: they use the same URLs, but what's returned is based on the HTTP Accepts
header. So if you were to visit, say, /users/bob
in your browser you'd see Bob's profile rendered in HTML, but if you called /users/bob
with the Accepts header set to application/json
you'd see Bob's profile as a JSON object.
The Web frontend is all client-side routing, so basically what I want to do is this: whatever the actual request path is, if the accepts header is not application/json
, it should simply return the static HTML homepage (where all routing is handled by React Router); if the accepts
header is application/json
, it should pass it on to the rest of the route logic.
I've tried it this way:
import fs from "fs/promises";
const router = express.Router();
const staticPage = await fs.readFile("./frontend/dist/index.html", "utf-8"); // This just returns the index.html file
router.use(async (req, res, next) => {
if (req.headers.accept != "application/json") {
res.status(200).send(staticPage);
} else {
router.get("/", async (req, res) => {
res.status(200).json({ hello: "world" });
});
router.get("/users/:id", async (req, res) => {
res.status(200).json({ id: req.params.id });
});
//Etc etc
}
next();
})
but this gives me Error: Can't set headers after they are sent.
What am I missing here? How do I just tell the router to serve the static HTML page if the accepts
header is anything but application/json
but do the complex routing logic if it is? Do I need to put next()
within my conditional block? None of the other related answers seem to be quite the same situation.
(And yes, I realize this is a weird way of handling app structure and that I need to deal with loading images or CSS or any other files as well, but that's my next step. First I need to sort this out.)
I'm trying to figure out how to do something a bit unusual: basically I have a server that's both a combination Web frontend with client-side routing and a JSON-based API with server-side routing, but here's the tricky part: they use the same URLs, but what's returned is based on the HTTP Accepts
header. So if you were to visit, say, /users/bob
in your browser you'd see Bob's profile rendered in HTML, but if you called /users/bob
with the Accepts header set to application/json
you'd see Bob's profile as a JSON object.
The Web frontend is all client-side routing, so basically what I want to do is this: whatever the actual request path is, if the accepts header is not application/json
, it should simply return the static HTML homepage (where all routing is handled by React Router); if the accepts
header is application/json
, it should pass it on to the rest of the route logic.
I've tried it this way:
import fs from "fs/promises";
const router = express.Router();
const staticPage = await fs.readFile("./frontend/dist/index.html", "utf-8"); // This just returns the index.html file
router.use(async (req, res, next) => {
if (req.headers.accept != "application/json") {
res.status(200).send(staticPage);
} else {
router.get("/", async (req, res) => {
res.status(200).json({ hello: "world" });
});
router.get("/users/:id", async (req, res) => {
res.status(200).json({ id: req.params.id });
});
//Etc etc
}
next();
})
but this gives me Error: Can't set headers after they are sent.
What am I missing here? How do I just tell the router to serve the static HTML page if the accepts
header is anything but application/json
but do the complex routing logic if it is? Do I need to put next()
within my conditional block? None of the other related answers seem to be quite the same situation.
(And yes, I realize this is a weird way of handling app structure and that I need to deal with loading images or CSS or any other files as well, but that's my next step. First I need to sort this out.)
Share Improve this question asked Feb 7 at 12:53 Joshua EllisJoshua Ellis 11 silver badge New contributor Joshua Ellis is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.1 Answer
Reset to default 2You have a couple of problems here.
In the not json case, you are calling next()
after you have already called send()
. Calling next()
lets the next handler in the chain run, if it calls send()
you will get the "...set headers..." error.
Also, every time your use()
handler is called, you add the two get()
handlers unnecessarily to the chain of handlers again.
Your code should look something like this:
router.use(async (req, res, next) => {
if (req.headers.accept != "application/json") {
res.status(200).send(staticPage);
} else {
next();
}
})
router.get("/", async (req, res) => {
res.status(200).json({ hello: "world" });
});
router.get("/users/:id", async (req, res) => {
res.status(200).json({ id: req.params.id });
});