Node.js Express Temporary File Serving - node.js

I'm trying to do a reverse image search using googlethis on an image the user uploads. It supports reverse image searching, but only with a Google-reachable image URL. Currently, I upload the image to file.io, which deletes it after it gets downloaded.
This is the current application flow:
User POSTs file -> Server uploads file to file.io -> Google downloads the file -> Server does things with the reverse image search
However, I want to skip the middleman and have Google download files directly from the server:
User POSTs file -> Server serves file at unique URL -> Google downloads the file -> Server deletes the file -> Server does things with the reverse image search
I've looked at Serving Temporary Files with NodeJs but it just shows how to serve a file at a static endpoint. If I added a route to /unique-url, the route would stay there forever (a very slow memory leak! Probably! I'm not really sure!)
The only way I can think of is to save each file with a UUID and add a parameter: /download?id=1234567890, which would probably work, but if possible, I want to do things in memory.
So:
How do I do this using normal files?
How do I do this in-memory?
Currently working (pseudo) code:
app.post('/', (req, res) => {
const imagePath = saveImageTemporarily(req)
const tempUrl = uploadToFileIo(imagePath)
const reverseImageResults = reverseGoogleSearch(tempUrl)
deleteFile(imagePath)
doThingsWithResults(reverseImageResults).then((result) => { res.send(result) })
}

The other answer is a good one if you are able to use Redis -- it offers lots of helpful features like setting a time-to-live on entries so they're disposed of automatically. But if you can't use Redis...
The basic idea here is that you want to expose a (temporary) URL like example.com/image/123456 from which Google can download an image. You want to store the image in memory until after Google accesses it. So it sounds like there are two (related) parts to this question:
Store the file in memory temporarily
Rather than saving it to a file, why not create a Buffer holding the image data. Once you're done with it, release your reference to the buffer and the Node garbage collector will dispose of it.
let image = Buffer.from(myImageData);
// do something with the image
image = null; // the garbage collector will dispose of it now
Serve the file when Google asks for it
This is a straightforward route which determines which image to serve based on a route parameter. The query parameter you mention will work, and there's nothing wrong with that. Or you could do it as a route parameter:
app.get('/image/:id', (req, res) => {
const id = req.params.id;
res.status(200).send(/* send the image data here */);
});
Putting it all together
It might look something like this:
// store image buffers here
const imageStore = {};
app.post('/image', (req, res) => {
// get your image data here; there are a number of ways to do this,
// so I leave it up to you
const imageData = req.body;
// and generate the ID however you want
const imageId = generateUuid();
// save the image in your store
imageStore[imageId] = imageData;
// return the image ID to the client
res.status(200).send(imageId);
});
app.get('/image/:id', (req, res) => {
const imageId = req.params.id;
// I don't know off the top of my head how to correctly send an image
// like this, so I'll leave it to you to figure out. You'll also need to
// set the appropriate headers so Google recognizes that it's an image
res.status(200).send(imageStore[imageid]);
// done sending? delete it!
delete imageStore[imageId];
});

I would use REDIS for the in-memory DB, and on the server, I would transform the image to base64 to store it in Redis.
In Redis, you can also set TTL on the images.
Check my code below
import {
nanoid
} from 'nanoid'
function base64_encode(file) {
// read binary data
var bitmap = fs.readFileSync(file);
// convert binary data to base64 encoded string
return new Buffer(bitmap).toString('base64');
}
app.post('/', async(req, res) => {
const client = redisClient;
const imagePath = saveImageTemporarily(req)
//const tempUrl = uploadToFileIo(imagePath)
var base64str = base64_encode(imagePath);
const id = nanoid()
await client.set(id, JSON.stringify({
id,
image: base64str
}));
const reverseImageResults = reverseGoogleSearch(JSON.parse(await client.get(id)).image)
await client.del(id);
doThingsWithResults(reverseImageResults).then((result) => {
res.send(result)
})
}

Related

Custom Computed Etag for Express.js

I'm working on a simple local image server that provides images to a web application with some JSON. The web application has pagination that will do a get request "/images?page=X&limit&200" to an express.js server that returns the JSON files in a single array. I want to take advantage of the browser's internal caching such that if a user goes to a previous page the express.js returns an ETAG. I was wondering how this could be achieved with express.js? For this application, I really just want the computation of the ETAG to take in three parameters the page, the directory, and the limit (It doesn't need to consider the whole JSON body). Also this application is for local use only, so I want the server to do the heavy lifting since I figured it be faster than the browser. I did see https://www.npmjs.com/package/etag which seems promising, but I'm not sure how to use it with express.js
Here's a boilerplate of the express.js code I have below:
var express = require('express');
var app = express();
var fs = require('fs');
app.get('/', async (req, res) =>{
let files = [];
let directory = fs.readdirSync("mypath");
let page = parseInt(req.query.page);
let limit = parseInt(req.query.limit);
for (let i = 0; i < limit; ++i) {
files.push(new Promise((resolve) => {
fs.readFile(files[i + page * limit].name, (err, data) => {
// format the data so easy to use for UI
resolve(JSON.parse(data));
});
});
}
let results = await Promise.all(files);
// compute an etag here and attach it the results.
res.send(results);
});
app.listen(3000);
When your server sends an ETag to the client, it must also be prepared to check the ETag that the client sends back to the server in the If-None-Match header in a subsequent "conditional" request.
If it matches, the server shall respond with status 304; otherwise there is no benefit in using ETags.
var serverEtag = "<compute from page, directory and limit>";
var clientEtag = req.get("If-None-Match");
if (clientEtag === serverEtag) res.status(304).end();
else {
// Your code from above
res.set("ETag", serverEtag);
res.send(results);
}
The computation of the serverEtag could be based on the time of the last modification in the directory, so that it changes whenever any of the images in that directory changes. Importantly, this could be done without carrying out the fs.readFile statements from your code.

confused about node-localstorage

so I'm making a site with node js, and I need to use localstorage, so I'm using the node-localstorage library. So basically, in one file I add data to it, and in another file I want to retrieve it. I'm not 100% sure about how to retrieve it. I know I need to use localStorage.getItem to retrieve it, but do I need to include localStorage = new LocalStorage('./scratch');? So I was wondering what the localStorage = new LocalStorage('./scratch'); did. So here is my code for adding data:
const ls = require('node-localstorage');
const express = require("express");
const router = express.Router();
router.route("/").post((req, res, next) => {
var localStorage = new ls.LocalStorage('./scratch');
if(req.body.name != undefined){
localStorage.setItem("user", req.body.name);
res.redirect('/')
}
else{
console.log("undefind")
}
});
module.exports = router;
If my question is confusing, I just want to know what var localStorage = new ls.LocalStorage('./scratch'); does.
A drop-in substitute for the browser native localStorage API that runs on node.js.
It creates an instance of the "localStorage" class, which this library provides. The constructor expects the location of the file, the scripts stores the key, value elements in.
Opinion: This looks pointless to me - I guess it fits your use case.

Node Express Fast CSV download to client

I've set up a small node js BE app, built with express and fastCsv module on top of it. The desired outcome would be to be able to download a csv file to the client side, without storing it anywhere inside the server, since the data is generated depending on user criteria.
So far I've been able to get somewhere it it, Im using streams, since that csv file could be pretty large depending on the user selection. Im pretty sure something is missing inside the code bellow:
const fs = require('fs');
const fastCsv = require('fast-csv');
.....
(inside api request)
.....
router.get('/', async(req, res) => {
const gatheredData ...
const filename = 'sometest.csv'
res.writeHead(200, {
'Content-Type': 'text/csv',
'Content-Disposition': 'attachment; filename=' + filename
})
const csvDataStream = fastCsv.write(data, {headers: true}).pipe(res)
})
The above code 'works' in some way as it does deliver back the response, but not the actual file, but the contents of the csv file, which I can view in the preview tab as a response. To sum up, Im trying to stream in that data, into a csv and push it to download file to client, and not store it on the server. Any tips or pointers are very much appreciated.
Here's what worked for me after created a CSV file on the server using the fast-csv package. You need to specify the full, absolute directory path where the output CSV file was created:
const csv = require("fast-csv");
const csvDir = "abs/path/to/csv/dir";
const filename = "my-data.csv";
const csvOutput = `${csvDir}/${filename}`;
console.log(`csvOutput: ${csvOutput}`); // full path
/*
CREATE YOUR csvOutput FILE USING 'fast-csv' HERE
*/
res.type("text/csv");
res.header("Content-Disposition", `attachment; filename="${filename}"`);
res.header("Content-Type", "text/csv");
res.sendFile(filename, { root: csvDir });
You need to make sure to change the response content-type and headers to "text/csv", and try enclosing the filename=... part in double-quotes, like in the above example.

How to use i18next in serverless node js?

I am using Node JS Azure functions. I am trying to internationalize the error messages returned by the functions with i18next. I could find examples with express or plain node server. In these cases middleware pattern can be used.
But for functions, I need a way to call i18next.t('key') with probably a language parameter which I am not able to find. Calling i18next.changeLanguage() before every call to i18next.t('key') doesn't seem practical.
My skeleton code is as follows
const i18next = require("i18next");
const backend = require("i18next-node-fs-backend");
const options = {
// path where resources get loaded from
loadPath: '../locales/{{lng}}/{{ns}}.json',
// path to post missing resources
addPath: '../locales/{{lng}}/{{ns}}.missing.json',
// jsonIndent to use when storing json files
jsonIndent: 4
};
i18next.use(backend).init(options);
exports.getString = (key, lang) => {
//i18next.changeLanguage(lang,
return i18next.t(key);
}
It is possible to fetch translations without doing changeLanguage each time?
As pointed out in the comments you need to call the i18next.changeLanguage(lang) function whenever the language needs to be defined or changed.
You can take a look to the documentation here.
The code could look like this
const i18next = require('i18next')
const backend = require('i18next-node-fs-backend')
const options = {
// path where resources get loaded from
loadPath: '../locales/{{lng}}/{{ns}}.json',
// path to post missing resources
addPath: '../locales/{{lng}}/{{ns}}.missing.json',
// jsonIndent to use when storing json files
jsonIndent: 4
}
i18next.use(backend).init(options)
exports.getString = (key, lang) => {
return i18next
.changeLanguage(lang)
.then((t) => {
t(key) // -> same as i18next.t
})
}

How to create a dynamic sitemap with mongodb, node.js, express and EJS?

I'm trying to create a dynamic sitemap for my website, it has lots of pages that change often.
The sitemap needs to be accessed from www.mywebsite.com/sitemap.xml
My current attempt queries the database for all the pages, get's each pages url and passes it to an EJS template that creates what looks like XML.
I have two problems here
The route to the page cannot have a file suffix. e.g. '.xml'
The page is automatically treated as html
I realise that there are other options for creating a sitemap using modules like "express-sitemap," but I haven't been able to find any easily understood (i am new to this) documentation for them, and this seems like a good way of doing things to me
yes you can use express-sitemap
To generate Sitemap automatically
var sitemap = require('express-sitemap')();
var app = require('express')();
sitemap.generate(app);
To generate dynamically..
for suppose you have products pages and you have specified url for them..
You can create a dynamic file everytime and place it in your public folder.
const Product = require('./model/product')
const sitemap = require('sitemap');
let sitemapData;
const generateSitemap = async () => {
const products = await Product.find({},{path: 1});
const urls = products.map({path} => `/products/${path}`)
sitemapData = sitemap.createSitemap ({
hostname: 'http://example.com',
cacheTime: 600000, // 600 sec - cache purge period
urls
});
}
You can use this function in a routine or with a cron and generate sitemap regularly..
setInterval(generateSitemap, 360000); //running every hour
other thing that you can do is:
use sitemapData variable and do things like this.
sitemapData.add({url: '/product-a/'}); // when some product is added
sitemapData.add({url: '/product-b/', changefreq: 'monthly', priority: 0.7});
sitemapData.del({url: '/product-c/'}); // when something is removed
sitemapData.del('/product-d/');
you can serve it in a route like this:
app.get('/sitemap.xml', function(req, res) {
sitemapData.toXML( function (err, xml) {
if (err) {
return res.status(500).end();
}
res.header('Content-Type', 'application/xml');
res.send( xml );
});
});
Here is how I made a txt sitemap. I find Google Search Console has an easier time fetching txt sitemaps vs xml sitemaps. But if you want to make a xml sitemap, you can look at this blog for the correct formatting. This code uses Mongoose and is saved as /pages/sitemap.txt.js.
// pages/sitemap.txt.js
import dbConnect from "../utils/dbConnect";
import Pduct from "../models/Pduct";
const createSitemap = (posts) => `${posts
.map(({ slug }) => {
return `https://[YOUR DOMAIN]/${slug}`;
})
.join("\n")}
`;
export async function getServerSideProps({ res }) {
await dbConnect();
const request = await Pduct.find({}, "slug").lean();
res.setHeader("Content-Type", "text");
res.write(createSitemap(request));
res.end();
}
export default () => null;

Resources