Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to synchronously load a csv file into memory before handling HTTP requests

I am trying to write a simple express/node.js app that responds to GET requests using data found in a csv file. I would like to read this csv file to generate a javascript object (essentially a key-value mapping), and then make that generated map available for the HTTP request handling logic in the controller.

I wrote a module that reads the csv files and exports the desired objects, but I'm not sure how to ensure:

  1. This operation completes and the objects actually exist before HTTP requests are handled
  2. The file operation is performed only a single time when the server starts up and not once per request incurring massive overhead

How can I organize my code to meet these goals in the context of an express app?

This is how I am processing the CSV file:

var myMap = {};

fs.createReadStream('filename.csv')  
  .pipe(csv())
  .on('data', (row) => {
    // Build javascript object
    myMap[row['key']] = row['value'];
  })
  .on('end', () => {
    console.log('Done.');
  });

// Does this work?
module.exports =  myMap;
like image 973
bsheets Avatar asked Mar 28 '19 05:03

bsheets


4 Answers

How about ensuring http object listens after the file is loaded into memory:


// server.js
var myMap = {};

function readCsv(cb){
  fs.createReadStream('filename.csv')  
  .pipe(csv())
  .on('data', (row) => {
    // Build javascript object
    myMap[row['key']] = row['value'];
  })
  .on('end', () => {
    console.log('Done.');
    cb();
  });
}

var app = express();

exports = Object.freeze({
  server: http.createServer(app)
  init(){
    readCsv(() => {
      this.server.listen(80)
    })
  }
})

Something like that.

You can also utilize Promise

// server.js
var myMap = {};

function readCsv(){
  return new Promise((resolve, reject) => {
    fs.createReadStream('filename.csv')  
    .pipe(csv())
    .on('data', (row) => {
      // Build javascript object
      myMap[row['key']] = row['value'];
    })
    .on('end', () => {
      console.log('Done.');
      resolve();
    })
    .on('error', reject)
  })

}

var app = express();

exports = Object.freeze({
  server: http.createServer(app)
  init(){
    return readCsv().then(() => {
      this.server.listen(80)
    })
  }
})

like image 108
Daniel Jee Avatar answered Oct 31 '22 14:10

Daniel Jee


I would look for more synchronous way to read file and handle http request. Here is sample code of what it should look like,

import fs from 'fs';

async function processCSV() {
    try {
        let map = await readCsv();
        //handle http request in another function with same async await way
        let http = await processHttpRequest(map);

        // process  the http response
    } catch (e) {
        console.log('e', e);
    }

}

function readCsv()
{
    let myMap = [];
    fs.createReadStream('filename.csv')
        .pipe(csv())
        .on('data', (row) => {
            // Build javascript object
           return myMap[row['key']] = row['value'];
        })
        .on('end', () => {
            console.log('Done.');
        });
}
async function processHttpRequest(map)
{
    try
    {
        let reqres = await httpReuqest(map); // Your defined function for httpReuqest
    }
    catch (e)
    {

    }

}
processCSV();
processHttpReuqet();
like image 21
Sohan Avatar answered Oct 31 '22 13:10

Sohan


In order to meet both of your goals, you can include the code in the app.js file. App.js only runs when the express server starts. It doesn't reload on page refresh. You can run app.listen after the readstream ends.

var myMap = {};

fs.createReadStream('filename.csv')  
  .pipe(csv())
  .on('data', (row) => {
    // Build javascript object
    myMap[row['key']] = row['value'];
  })
  .on('end', () => {
    app.listen(port, () => console.log(`Example app listening on port ${port}!`));
  });

However, since I don't think you're going to have a lot of data, it's better to use a synchronous (blocking) methods, for both the csv parser and file reader. This just makes it easier to understand. I use csv-parse below.

const express = require('express')
const fs = require('fs')
const parse = require('csv-parse/lib/sync')

const app = express()
const port = 3000

/* In this example assume myMap will be
/ `
/ "key_1","key_2"
/ "value 1","value 2"
/ `
*/
var myMap = fs.readFileSync('sample.csv', 'utf8');

/* parsing the csv will return:
/  [Object {key_1: "value 1", key_2: "value 2"}]
*/
const records = parse(myMap, {
  columns: true,
  skip_empty_lines: true
})

app.get('/', (req, res) => res.send('Hello World!' + records[0].key_1))
app.listen(port, () => console.log(`Example app listening on port ${port}!`))

test it on runkit

like image 42
pooya72 Avatar answered Oct 31 '22 14:10

pooya72


Update:

use https://csv.js.org/parse/

Below one is deprecated, not maintained anymore.

Deprecated:

Hi I have created an npm package to read CSV synchronously or as a promise :

https://www.npmjs.com/package/csv-parser-sync-plus-promise

Description:

csv-parser-sync-plus-promise

A module to read csv synchronously or as promise

Features

now read any csv synchronously or as promise. Choice is yours

Usage

let parser = require('csv-parser-sync-plus-promise')

// for sync

let a=parser.readCsvSync('<filepath>')

// for promise

let b=parser.readCsvPromise('<filepath>')

**Note:** You can use both fully qualified and relative paths <filepath>

Errors

All errors will be printed as console.error and the process will exit with exit code 222

like image 37
PDHide Avatar answered Oct 31 '22 14:10

PDHide