How to send piped response in Next.js API using wkhtmltoimage? - javascript

I'm new to Next.js, and I'm trying to use wkhtmltoimage but I can't seem to send the generated image stream as a response in my Next.js API.
const fs = require('fs')
const wkhtmltoimage = require('wkhtmltoimage').setCommand(__dirname + '/bin/wkhtmltoimage');
export default async function handler(req, res) {
try {
await wkhtmltoimage.generate('<h1>Hello world</h1>').pipe(res);
res.status(200).send(res)
} catch (err) {
res.status(500).send({ error: 'failed to fetch data' })
}
}
I know I'm doing plenty of stuff wrong here, can anyone point me to the right direction?

Since you're concatenating __dirname and /bin/wkhtmltoimage together, that would mean you've installed the wkhtmltoimage executable to ./pages/api/bin which is probably not a good idea since the pages directory is special to Next.js.
We'll assume you've installed the executable in a different location on your filesystem/server instead (e.g., your home directory). It looks like the pipe function already sends the response, so the res.status(200).send(res) line will cause problems and can be removed. So the following should work:
// ./pages/api/hello.js
const homedir = require("os").homedir();
// Assumes the following installation path:
// - *nix: $HOME/bin/wkhtmltoimage
// - Windows: $env:USERPROFILE\bin\wkhtmltoimage.exe
const wkhtmltoimage = require("wkhtmltoimage").setCommand(
homedir + "/bin/wkhtmltoimage"
);
export default async function handler(req, res) {
try {
res.status(200);
await wkhtmltoimage.generate("<h1>Hello world</h1>").pipe(res);
} catch (err) {
res.status(500).send({ error: "failed to fetch data" });
}
}

Related

express error handler not working with the given http status code and custom message

When I am entering localhost://5000/products/
Its showing me all the json objects
But when I am entering localhost://5000/products/(some random id's) It must show me the 404 error with my custom message but its not showing me that.
This is my code:
import express from 'express'
import asyncHandler from 'express-async-handler'
const router = express.Router()
import Product from '../models/productModel.js'
// #Desc Fetch all products
// #Desc GET /api/products
// #access Public
router.get(
'/',
asyncHandler(async (req, res) =>{
const products = await Product.find({})
res.json(products)
}))
// #Desc Fetch single products
// #Desc GET /api/products:id
// #access Public
router.get(
'/:id',
asyncHandler(async (req, res) =>{
const product = await Product.findById(req.params.id)
if(product) {
res.json(product)
} else {
res.status(404).json({ message: 'Product not found' })
}
}))
export default router
Your question is somewhat lacking in details, but I do believe I know what's happening. Your idea is that if you pass a non-existent id to the route and mongo does not find it, it will return a 404 - Not Found. Solid logic.
The reason you're not seeing that 404 is, most likely, because you are calling the route with something like /products/dflhdshfd. The problem with that is that findById only accepts strings that can be cast to an ObjectId, which, of course dflhdshfd cannot be. Hence when you call your route with /products/dflhdshfd, your app bombs and throws this error:
CastError: Cast to ObjectId failed for value "dflhdshfd" at path "_id" for model "product"
And your app does not return anything because you don't handle that error.
As a test, try calling the route like this: /products/601977b2d2e3cf756b85bc61 and you will see your 404, because 601977b2d2e3cf756b85bc61 can be cast to a valid ObjectId.
But more generally, you need to catch errors coming from Product.findById(req.params.id) by doing something like this:
const product = await Product.findById(req.params.id).catch(e => false);
if(product) {
res.json(product)
} else {
res.status(404).json({ message: 'Product not found' })
}
This way when your findById throws an error, it will return false to the product variable and your app will return a 404. (This is not a great way to handle errors and is simply meant to illustrate the point)
Added
Properly handling errors can be a daunting task in a large application, so I will address your specific case only while encouraging you to go over this article for a more complete picture.
The way I would handle this specific error is I would first create a small function to route all the caught errors to. Something simple like this:
const errorHandler = (err, res) => { //res is the response object
console.log(err);
res.status(400).json({msg: "Error has occurred"})
}
Then I would rewrite your code like this:
router.get(
'/:id',
(req, res) => {
Product.findById(req.params.id)
.then(product => {
if(product) {
res.json(product)
} else {
res.status(404).json({message: 'Product not found' })
}
})
.catch(e => errorHandler(e, res))
})
Whenever findById throws an error, it simply sends it over to errorHandler to deal with, while errorHandler dumps it to the console for you to troubleshoot and also send a response back to the user.
And that can be the template you adopt for ALL your queries (don't forget to always catch db query errors). Later you can, for example, add a logging feature to errorHandler that dumps errors into a file for you to later look at.
So this would be the basic approach I would take here.

How to retrieve image from nodejs backend to react

I have uploaded image by using multer library in express.
in path Backend->Uploads/
and stored image path in mongodb.
I have project structure as
DirectoryName
Backend
Uploads
Frontend
I can get the image path in frontend component , but How to get actual images from backend folder.
Can I use file moving to store it in public in frontend , or retrieve stream from server.
Will moving File from backend to frontend works actually in deployment.
I think you have done most of the work. Just set the image source or uri to the path and it will serve the image.
A simple example of implementation from one of my projects.
Mayby not the best, but it works and, most important, you can get the idea.
In this case I keep files on server.
Created a route in my API:
router.get('/download/:photo_id', async (req, res) => {
try {
const photo = await Photo.findOne({
photoID: req.params.photo_id,
});
if (!photo) {
return res.status(404).json({ msg: 'Photo not found' });
}
const filename = photo.photoFileName;
const downloadPath = path.join(__dirname, '../../../imgs/', `${filename}`);
res.download(downloadPath);
} catch (err) {
console.error(err.message);
if (err.kind === 'ObjectId') {
return res.status(404).json({ msg: 'Photo not found' });
}
res.status(500).send('Server error');
}
});
And this route is called from front something like this:
const handleDownload = async () => {
const res = await fetch(`/api/photo/download/${photoID}`);
const blob = await res.blob();
download(
blob,
`${photoID}-${title}-${contributorName}.jpg`
);
};

next.js app with custom server is not rendering correctly

I'm new to next.js so maybe I'm missing something very stupid. I want to use custom routes so I created a server.js file and changed my package.json command to node server.js. This is the entire server.js file:
const express = require("express");
const next = require("next");
const createLocaleMiddleware = require("express-locale");
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
const handle = app.getRequestHandler();
app
.prepare()
.then(() => {
const server = express();
server.get("/", createLocaleMiddleware(), (req, res) => {
res.redirect(`/${req.locale.language}/home`);
});
server.get("/:lang/home", (req, res) => {
const actualPage = "/";
const queryParams = { locale: req.params.lang };
app.render(req, res, actualPage, queryParams);
});
server.listen(3000, err => {
if (err) throw err;
console.log("> Ready on http://localhost:3000");
});
})
.catch(ex => {
console.error(ex.stack);
process.exit(1);
});
I believe that according to the docs, this should work. I just want to render the index page with the users locale on the specified route ('/:lang/home'). I'm using react-intl for the i18n.
Now I get the following error in the console (client side):
It's in dutch but it's just saying it can't find any of the specified files. So now the HMR is not working anymore, routing is not working anymore (with Router.push). The only thing it does correctly is loading the index page (I can see it in the browser).
I also tried to enable and disable this flag from the docs:
module.exports = {
useFileSystemPublicRoutes: false
}
Sadly, no effect.
Am I missing something? Is it because I'm redirecting? Or is this not to way to handle routing? If someone could provide some pointers that would be great :)
Thanks in advance!
You are missing server.get('*', handle) as you can see in the custom server express example. This is absolutely required :)

Node Winston log file forced string conversion

In a Node project, I want to show the contents of a Winston log file in a React interface. Reading the file:
let content;
fs.readFile("path", "utf-8", function read(err, data) {
if (err)
throw err;
content = data;
});
I send them to the interface:
router.get("/", function (req, res) {
res.status(200).send(JSON.stringify(content));
});
And i get the content in a .jsx file:
getLogs().then(res => {
let datafromfile = JSON.parse(res);
// Use the data
return;
}).catch(err => {
return err.response;
});
The issue i am having is that fs converts all the data into a string (since i am putting the utf-8 encoding and do not want to be returned a buffer) and therefore i cannot manipulate the objects in the log file to show them structurally in the interface. Can anyone guide how to approach this problem?
I have not debugged this, but a lot of this depends on whether or not the the Winston file your loading actually has JSON in it.
If it does then JSONStream is your friend and leaning through or through2 is helpful you in node (streams).
following, code/pseudo
router.get("/", function (req, res) {
const logPath = ‘somePath’; // maybe it comes from the req.query.path
const parsePath = null; // or the token of where you want to attemp to start parsing
fs.createReadStream(logPath)
.pipe(JSONStream.parse(parsePath))
.pipe(res);
});
JSONStream
fs.createReadStream and node docs
through2

Error cannot be caught with Express.js and gridfs-stream

It's an easy image (picture) download server, Express.js receives the request, gets an image from MongoDB GridFS, and responds with the file.
It's OK when request is valid (when the requested file exists).
The problem is that I cannot catch the MongoError when the query failed (i.e. requested image does not exist).
import Grid from 'gridfs-stream'
const root = 'fs_images'
// This func returns the file stream
export function getReadStream (id) {
const gfs = Grid(mongoose.connection.db, mongoose.mongo)
const options = {
_id: id,
mode: 'r',
root: root
}
const readStream = gfs.createReadStream(options)
readStream.on('error', function (err) {
// throw here
// it performs the same without this on-error hook;
// if comment the `throw err`, nothing will happens
// but I want the caller knows the error
throw err
})
return readStream
}
And this is the router
router.get('/:fileId', function (req, res, next) {
const fileId = req.params.fileId
try {
const imgReadStream = image.getReadStream(fileId)
imgReadStream.pipe(res)
} catch (err) {
// nothing catched here
// instead, the process just crashed
console.log(err)
}
}
And I just cannot catch the err. When I try to request something that doesn't exist the MongoError shows in the console, and the app crashes with errno is 1.
Head of console output:
/.../node_modules/mongodb/lib/utils.js:123
process.nextTick(function() { throw err; });
^
MongoError: file with id 123456123456123456123456 not opened for writing
at Function.MongoError.create (/.../node_modules/mongodb-core/lib/error.js:31:11)
This may be a bit different. If somewhere else throws an Error it will be caught by my error-handler (app.use(function(err, req, res, next){ /* ... */})), or at least by the default handler from Express.js, and returns a 500, without the process crashing.
In short, I want the app to know and catch this MongoError so I can handle it manually (i.e. return a 404 response).
try/catch won't work because the error is happening in a different tick (asynchronously). Perhaps you could listen for the error in the router instead?
const imgReadStream = image.getReadStream(fileId)
imgReadStream.on('error', function(err) {
// Handle error
});
imgReadStream.pipe(res)

Categories