NodeJS + React + Next Framework adding a path to the route - node.js

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

Related

Why is my Heroku Express API data persistent even though it's only coming from a variable

I created a simple API using express, and deployed it to Heroku, this is the code for it:
const express = require("express");
const app = express();
const cors = require("cors");
app.use(express.json());
app.use(cors());
app.use(express.static("build"));
let notes = [
{
id: 1,
content: "HTML is easy",
date: "2022-05-30T17:30:31.098Z",
important: true,
},
{
id: 2,
content: "Browser can execute only Javascript",
date: "2022-05-30T18:39:34.091Z",
important: false,
},
{
id: 3,
content: "GET and POST are the most important methods of HTTP protocol",
date: "2022-05-30T19:20:14.298Z",
important: true,
},
];
const generateId = (arr) => {
const maxId = arr.length < 0 ? 0 : Math.max(...arr.map((item) => item.id));
return maxId + 1;
};
app.get("/", (req, res) => {
res.send(`<h1>Hello World!</h1>`);
});
app.get("/api/notes", (req, res) => {
res.json(notes);
});
app.get("/api/notes/:id", (req, res) => {
const id = Number(req.params.id);
const note = notes.find((note) => note.id === id);
if (note) {
res.json(note);
} else {
res.status(404).end();
}
});
app.delete("/api/notes/:id", (req, res) => {
const { id } = Number(req.params);
notes = notes.filter((note) => note.id !== id);
res.status(204).end();
});
app.post("/api/notes", (req, res) => {
const body = req.body;
if (!body.content) {
return res.status(400).json({
error: "Content Missing",
});
}
const note = {
content: body.content,
important: body.important || false,
date: new Date(),
id: generateId(notes),
};
notes = notes.concat(note);
res.json(note);
});
app.put("/api/notes/:id", (req, res) => {
const newNote = req.body;
notes = notes.map((note) => (note.id !== newNote.id ? note : newNote));
res.json(newNote);
});
const PORT = process.env.PORT || 3001;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
as you can see, the data served to the frontend (A React app) comes from the '/api/notes' endpoint, this endpoint returns a response with the notes array.
After deploying to Heroku (https://fierce-chamber-07494.herokuapp.com/) the functionality of adding notes, and setting importance all work perfectly normal, but what I wasn't expecting was for the data to be persistent even after refreshing the page, visiting it in another device, etc. The data only comes from a variable, not a database, nothing. So why is it persistent? does Heroku modify the variable itself? how does this work?
The top-level code of an Express server often runs once, when you start up the server. Variables declared at that top level are then persistent if there are any handlers that reference them.
Consider how a client-side page with JavaScript works - the page loads, and then the JavaScript runs. If you keep the tab open for a couple hours and then come back to it, you'll see that variables declared on pageload still exist when you come back. The same sort of thing is happening here, except that the persistent environment is on your server, rather than on a client's page.
The code that starts up the Express server - that is, your
const express = require("express");
const app = express();
const cors = require("cors");
app.use(express.json());
app.use(cors());
...
and everything below it - doesn't run every time a request is made to the server. Rather, it runs once, when the server starts up, and then when requests are made, request handlers get called - such as the callback inside
app.get("/", (req, res) => {
res.send(`<h1>Hello World!</h1>`);
});
So, the variables declared at the top-level are persistent (even across different requests) because that server environment is persistent.
That said - something to keep in mind with Heroku is that with their free and cheap tiers, if no request is made to your app for a period of time (maybe 30 minutes), Heroku will essentially turn your server off by spinning down the dyno until another request is made, at which point they'll start your server up again, which will run the top-level code again. So while you'll sometimes see a top-level variable that appears to have its mutated values persist over multiple requests, that's not something to count on if your Heroku plan doesn't guarantee 100% uptime for your server.

How to organize express endpoints properly

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"}%

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://');

My React front-end is unable to call my node-express backend server, the fullstack app is deployed in heroku

const port = process.env.PORT || ("http://localhost:3002")
const postUrl=`${port}/post`;
addData=()=>{
console.log(postUrl)
console.log(process.env,"AS")
Axios.post(postUrl,this.state.form)
.then((response)=>{
this.setState({errorMessage:"",successMessage:response.data})})
.catch((err)=>{
this.setState({successMessage:"",errorMessage:err.response.data.message})
})
}
When I am calling the backend in the production process.env.PORT is blank.
(process.env.NODE_EV= production which is absolutely correct exactly like in the backend)
My backend is completely fine as it getting the process.env.PORT correctly.
But my frontend is not getting the process.env.PORT that's why it keeps calling the other address("http://localhost:3002").
App will completely work fine if I keep open my local machine backend because the "http://localhost:3002"
is available to serve. But in production, Heroku keeps changing the process.env.PORT which is showing its value in the backend, not in the frontend
How can I make my frontend to call my backend server properly in production??
const express = require("express");
const app = express();
const bodyParser = require("body-parser");
const port = process.env.PORT || 3002;
const cors = require("cors");
const path=require("path");
const routing = require("./routing/route");
require('dotenv').config();
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use("/",routing);
app.use(function (err, req, res, next) {
console.log("hii")
res.status(500).send({ message: err.message });
});
if(process.env.NODE_ENV ==="production"){
app.use(express.static("client/build"));
app.get("*",(req,res)=>{
res.sendFile(path.resolve((__dirname,"client","build","index.html")));
});
}
app.listen(port, () => {
console.log(` Server is started at http://localhost:${port}`);
});
server file
If your React application is served from your Node.JS application as you said, you could just use window.location. window.location is an object that stores statistics about the current page that the user is on, and you could use that to construct a URL and send the server a request, like so:
// This URL uses a template literal, which is a new feature of ES6.
// All but Internet Explorer supports it.
// This is using window.location.protocol, which is either `http:` or `https:`,
// depending on the protocol that the page was loaded with. window.location.host
// is the host that the page was loaded from, with the port number.
const postUrl =
`${window.location.protocol}//${window.location.host}/post`;
// And then requesting with the URL.
addData = () => {
console.log(postUrl);
console.log(process.env, "AS");
Axios.post(postUrl, this.state.form)
.then((response) => {
this.setState({errorMessage: "",successMessage: response.data});
})
.catch((err) => {
this.setState({successMessage: "",errorMessage: err.response.data.message});
});
}

NextJS does not recognize dynamically added static assets in production

I'm running a two container docker setup; NextJS for the public facing web, and Django backend for admins to add content. The routes are nicely working with getInitialProps which fetches the added content. As the content references static images, they are connected via docker volumes (./static/media on django container and ./public/media on nextjs container).
However when a new image appears in ./public/media, the started NextJs server returns a 404 response for those images.
// EDITED SOLUTION: As suggested by #Pierfrancesco
The workaround solution is to create a custom server which dynamically serves those files
// server.js
const express = require('express')
const next = require('next')
const port = parseInt(process.env.PORT, 10) || 3000
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare().then(() => {
const server = express()
server.get('/media/images/*', (req, res) => {
// Disallow travelling up in the file tree
let target = req.originalUrl.replace("..", "")
return res.sendFile(__dirname + '/public' + target);
})
server.all('*', (req, res) => {
return handle(req, res)
})
server.listen(port, err => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})
Could this be a feature request or is this a bug in NextJS?

Resources