How to access next.js rendered HTML via custom server - javascript

I want a server side generated page in next.js to be served as a file. So I wanted to grab the rendered content inside a custom server.js file:
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('/', async (req, res) => {
const nextResponse = await app.renderToHTML(req, res, '/', req.query);
console.log('nextResponse', nextResponse);
console.log('res.body', res.body);
});
server.get('*', (req, res) => {
return handle(req, res);
});
server.listen(port, (err) => {
if (err) throw err;
console.log(`> Ready on http://localhost:${port}`);
});
});
Oddly enough every console.log returns null or undefined.
I thought that renderToHTML would just return the rendered HTML string. Is there any way to do this?

This one is a bit tricky but achievable.
The idea is to override res.end function in order to catch rendered HTML there. The tricky part is that Next.js gzips and streams response using the compression library that's overriding res.end somewhere in the middle of the handle function.
The compression library is initialized using the handleCompression function of the Next.js's Server object (which is accessible using the app.getServer()), so that function needs to get overridden too.
So it should be looking something like this:
const { parse } = require('url');
const next = require('next');
const express = require('express');
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const port = process.env.PORT || 3000;
const handle = app.getRequestHandler();
app.prepare()
.then(() => {
const server = express();
server.get('*', async (req, res) => {
const parsedUrl = parse(req.url, true);
const nextServer = await app.getServer();
const _handleCompression = nodeServer.handleCompression.bind(nodeServer);
nextServer.handleCompression = (req, res) => {
_handleCompression(req, res);
const _resEnd = res.end.bind(res)
res.end = function (payload) {
console.log('Rendered HTML: ', payload);
return _resEnd(payload);
}
}
return handle(req, res, parsedUrl);
});
server.listen(port, err => {
if (err) throw err;
console.log('> Ready on http://localhost:' + port);
});
});
After you get rendered HTML you don't have to use the saved _resEnd function. Feel free to manipulate, serve as a file, or whatever you want with it.

Related

How do I make Axios show css?

I am using axios to practice Web Scraping by making a Web Viewer, and I noticed that the CSS Wasn't Loading.
I used this code:
console.log("Tribble-Webviewer is starting!")
const express = require('express')
const app = express()
const port = 3000
const publicDir = app.use(express.static('public'))
var cheerio = require('cheerio'); // Basically jQuery for node.js
const axios = require('axios').default;
const rp = require('request-promise');
const url = 'https://pointless.com/';
app.get('/', (req, res) => {
app.use('/static', express.static('public'))
})
app.get('/test', (req, res) => {
axios.get(url)
.then(({ data }) => res.send(data))
})
app.listen(port, () => {
console.log(`Tribble-Pro is listening on port ${port}`)
})
If you load the /test page, the CSS does not show.
Example of the CSS not loading below:
Image
I used this async function:
async function getCssTest() {
try {
const response = await axios.get(urlplusstyle);
res.send(response)
} catch (error) {
console.error(error);
}
}

NodeJS: Server.on-listenerfunction doesn't exist when exporting?

I am using server.js to start the server and router.js to listen to the requests. When I try to access the server, nothing happens and I can't reach it, I assume so, because the server.on function doesn't execute. Everything works, when I put it just in the server.js tho, so the error seems to be in with the exports/imports, but I can't figure out what it is.
server.js:
const fs = require("fs");
const http = require("http");
const template = require("./template.js");
const port = 3000;
const server = http.createServer();
server.listen(port, () => {
console.log(`Server listening on localhost:${port}`);
})
module.exports = server;
router.js:
const server = require("./server.js");
const template = require("./template.js");
server.on("request", (req, res) => {
let url = req.url;
let method = req.method;
let html;
console.log("received req");
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
if(url.endsWith("/feedback") && method == "GET") {
html = template.createHtml("Liste der Feedbacks");
} else {
html = template.createHtml(`Methode: ${method}, URL: ${url}`);
}
res.end(html);
})
If you are looking to import the router.js and have the server object accessible in that file and execute the on method on it, one way to do it is by passing the server while requiring the router.js file as follows:
server.js:
const fs = require("fs");
const http = require("http");
const port = 3000;
const server = http.createServer();
const router = require("./router.js")( server );
server.listen(port, () => {
console.log(`Server listening on http://localhost:${port}`);
});
router.js:
const template = require("./template.js");
module.exports = function handleRequest( server ){
return server.on("request", (req, res) => {
let url = req.url;
let method = req.method;
let html;
console.log("received req");
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
if(url.endsWith("/feedback") && method == "GET") {
html = template.createHtml("Liste der Feedbacks");
} else {
html = template.createHtml(`Methode: ${method}, URL: ${url}`);
}
res.end(html);
})
}
Whatever pattern you try, make sure to avoid Circular dependencies.
Also, as a general recommendation, I would suggest that you try and avoid tight coupling in modules such as these whenever possible. Ideally, the router module should not be tightly coupled to the server module.

close server from within router

I create an express app like this
const express = require('express')
const app = express();
app.use(express.static(path.join(__dirname, 'public')));
app.post('/close', async (_, res) => {
res.status(200);
res.end();
app.close();
});
module.exports = app;
I instantiate it in another module
const myApp = require('./app.js');
myApp.listen(port, () => {
console.log(`Started server on ${port}`);
});
I want the server to shut itself down when it receives a post request to /close. At the moment, I just get a app.close is not a function error.
I know I can close a server externally like this
const server = myApp.listen(port, () => {
console.log(`Started server on ${port}`);
});
server.close();
but I want to close the server on a post request to /close, how can I do that?
To get access to your server object, try using
req.connection.server
from within your route handler.
.close() makes the server stop listening for new connections. Already-established connections are not affected. The server object emits a 'close' event when all open connections have disconnected.
process.exit() stops your whole nodejs app.
So, this code might do what you want.
app.post('/close', (req, res, next) => {
res.status(200)
res.end()
const server = req.connection.server
if (server.listening) {
server.addEventListener('close', ev => {
console.log('server closed. See ya later alligator.')
process.exit(0)
})
console.log('closing server')
server.close()
}
});
If you need to get the server object from your app object (if getting it from your req object isn't good enough), you could put in a little middleware function like this.
app.use( function adornApp( req, res, next ) {
req.app.locals.server = req.connection.server
next()
} )
Then you'll be able to get your server from app.locals.server once your middleware is first invoked.
You could use the http-terminator package to close your server. The following should do the trick. The reason we use the http-terminator is because the server won't close if it is visited via a route.
const { createHttpTerminator } = require("http-terminator");
const { initApp, app } = require("./app.js");
const PORT = 3000;
const server = app.listen(PORT, () => {
console.log(`Started server on ${PORT}`);
});
const httpTerminator = createHttpTerminator({ server });
initApp(httpTerminator);
Inside the module:
const express = require("express");
const app = express();
const initApp = (httpTerminator) => {
app.get("/close", async (_, res) => {
res.json({ message: "we closed" });
await httpTerminator.terminate();
});
};
module.exports = { initApp, app };

How do I make an axios call to the relative path(s) using express router?

I'm learning the MERN stack and creating the typical beginner "Todo" App. When I make an axios call such as axios.post("http://localhost:4000/todos/add", newTodo) it posts fine, but when I try axios.post("/todos/add", newTodo) it doesn't.
The call obviously only works locally - how do I fix this/what am I doing wrong?
Here is todos.js file located in /routes/api folder:
const todoRouter = require("express").Router();
let Todo = require('../../models/todo');
todoRouter.route("/").get(function (req,res){
Todo.find(function(err, todos){
if (err){
console.log (err)
} else {
res.json(todos)
}
});
});
todoRouter.route("/:id").get(function(req,res){
let id = req.params.id
Todo.findById(id, function(err,todo){
if (err) {
console.log(err)
} else {
res.json(todo)
}
});
});
todoRouter.route("/add").post(function(req,res){
let todo = new Todo(req.body)
todo.save()
.then(function (todo){
res.status(200).json({"todo": "todo added successfully"})
})
.catch(function (err){
res.status(400).json("adding new todo failed")
})
})
todoRouter.route('/update/:id').post(function(req, res) {
Todo.findById(req.params.id, function(err, todo) {
if (!todo)
res.status(404).send("data is not found");
else
todo.todo_description = req.body.todo_description;
todo.todo_responsible = req.body.todo_responsible;
todo.todo_priority = req.body.todo_priority;
todo.todo_completed = req.body.todo_completed;
todo.save().then(todo => {
res.json('Todo updated!');
})
.catch(err => {
res.status(400).send("Update not possible");
});
});
});
module.exports = todoRouter;
Here is my index.js located in /routes/api folder:
const router = require("express").Router();
const todoRoutes = require("./todos");
// Todo routes
router.use("/todos", todoRoutes);
module.exports = router;
Here is index.js located in the /routes folder:
const path = require("path");
const router = require("express").Router();
const todoRoutes = require("./api");
// API Routes
router.use(todoRoutes);
// If no API routes are hit, send the React app
router.use(function(req, res) {
res.sendFile(path.join(__dirname, "../client/build/index.html"));
});
module.exports = router;
Here is my server.js file:
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const cors = require('cors');
const mongoose = require('mongoose')
const PORT = process.env.PORT || 4000;
const routes = require('./routes')
// Define middleware here
app.use(cors());
app.use(express.urlencoded({ extended: true }));
app.use(bodyParser.json());
// Serve up static assets (usually on heroku)
if (process.env.NODE_ENV === "production") {
app.use(express.static("client/build"));
}
// Define API Routes
app.use(routes)
// Send every other request to the React app
// Define any API routes before this runs
app.get("*", (req, res) => {
res.sendFile(path.join(__dirname, "./client/build/index.html"));
});
//Connect to mongoose
mongoose.connect(process.env.MONGODB_URI || "mongodb://127.0.0.1:27017/todos", { useNewUrlParser: true });
const connection = mongoose.connection
connection.once("open", function () {
console.log("MongoDB database connection established successfully")
})
app.listen(PORT, function () {
console.log("Server is running on Port: " + PORT);
});
Just set "proxy": "http://localhost:4000" property in your package.json and all your /something requests will be directed towars your backend without having to specify the base url
you can set the baseUrl for axios to http://localhost:4000 and then relative paths should work. https://github.com/axios/axios#creating-an-instance

Router not firing .find or .findByID in express app. Using nextjs as well

I am using a NextJS/MERN stack. My NextJS is using my server.js file, along with importing the routes for my API. The routes appear to be working as they do show activity when firing an API call from Postman or the browser. However, this is where the activity stops. It's not getting passed the Model.find() function as far as I can tell. I am not sure if this has to do with Next js and the prepare method in the server.js, or if this is related to the bodyparser issue.
Here is my server.js
const express = require("express");
const urlObject = require('./baseURL')
const passport = require("./nextexpress/config/passport-setup");
const passportSetup = require("./nextexpress/config/passport-setup");
const session = require("express-session");
const authRoutes = require("./nextexpress/routes/auth-routes");
const KBRoutes = require("./nextexpress/routes/kb-routes");
const userRoutes = require('./nextexpress/routes/user-routes')
const pollRoutes = require('./nextexpress/routes/poll-routes')
const mongoose = require("mongoose");
const cookieParser = require("cookie-parser"); // parse cookie header
const next = require('next')
const dev = process.env.NODE_ENV !== 'production'
const nextapp = next({ dev })
const handle = nextapp.getRequestHandler()
const bodyParser = require('body-parser');
// mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/kb', { useNewUrlParser: true });
mongoose.connect('mongodb://localhost:27017/kb')
console.log(process.env.MONGODB_URI)
const connection = mongoose.connection;
const baseURL = urlObject.baseURL
const PORT = process.env.PORT || 3000
connection.once('open', function () {
console.log("MongoDB database connection established successfully");
})
nextapp.prepare().then(() => {
const app = express();
console.log(process.env.PORT, '----port here ----')
app.use(cookieParser());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use("/api/auth", authRoutes);
app.use("/api/kb", KBRoutes);
app.use('/api/user', userRoutes)
app.use('/api/poll', pollRoutes)
app.get('/posts/:id', (req, res) => {
return nextapp.render(req, res, '/article', { id: req.params.id })
})
app.get('/redirect/:id', (req, res) => {
return nextapp.render(req, res, '/redirect')
})
app.all('*', (req, res) => {
return handle(req, res)
})
app.listen(PORT, err => {
if (err) throw err
console.log(`> Ready on http://localhost:${PORT}`)
})
})
// connect react to nodejs express server
And the relevant route:
KBRoutes.get('/', (req, res) => {
console.log(KB.Model)
KB.find({}, (err, photos) => {
res.json(kbs)
})
})
I am able to get to each one of the routes. Before this was working, when I had the NextJS React portion split into a separate domain therefore separate server.js files. Once I introduced NextJs thats when this problem arose. Any help would be greatly appreciated.
It looks like the relevant route is trying to return json(kbs), but kbs doesn't seem to be defined. Returning the result of your find query would make more sense to me, including a nice error catcher and some status for good practice. Catching errors should tell you what's going wrong, i would expect an error in your console anyway that would help us out finding the answer even more.
KB.find({}, (err, photos) => {
if (err) res.status(401).send(err)
res.status(200).json(photos)
})

Categories

Resources