Data

In many situations we need to access data stored in different ways, such as text files, JSON files, SQL or NoSQL databases or even external web services.

In this section we show some examples of working with stored data.

Async to Sync

Node.js has the capability to manage asynchronous processes by default but HTTP protocol is always a synchronous process.

This means that if you are using a Node Module with asynchronous processing you will have to convert it into a synchronous process to return data when available.

To do this, you can use javascript Promises, async and await like this example:

function waitForData(data) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(data.split(''));
    }, 1000 * Math.random());
  });
}

return {
  GET: async function(props) {
    const data = await waitForData("1010111101");
    return getJSON(data);
  }
}


Good to know

This feature is only available in route handlers. Avoid using asynchronous data in pages or layouts.

JSON Files

Some of the easiest ways to store data is a JSON file.

Previous.js includes by default the Node.js File System Module, so we can use it to read and write JSON files like this.

In this example we will use sync processes to make it easer.

const jsonFile = path.join(
  process.cwd(), 
  'app/examples/api/json/db/todo.json'
);

return {
  GET: (props) => {
    if (!fs.existsSync(jsonFile)) {
      return getJSON([]);
    }
    const json = fs.readFileSync(jsonFile);
    const todoList = JSON.parse(json || '[]');
    return getJSON(todoList);
  },
  POST: ({ data }) => {
    if (!data.task) {
      return getJSON({
        error: true,
        message: 'Task is a required field.'
      });
    }
    const json = fs.readFileSync(jsonFile);
    const todoList = JSON.parse(json);
    if (!data.id) {
      // INSERT
      data.id = todoList[todoList.length - 1].id + 1;
      todoList.push({
        id: data.id,
        task: data.task,
        done: !!data.done
      });
    } else {
      // UPDATE
      todoList.map((item, index) => {
        if (data.id == item.id) {
          todoList[index] = {
            id: parseInt(data.id),
            task: data.task,
            done: !!data.done
          };
        }
      });
    }
    // SAVE
    fs.writeFileSync(jsonFile, JSON.stringify(todoList));
    return getJSON({
      success: true
    });
  },
  DELETE: ({ data }) => {
    if (!data.id) {
      return getJSON({
        error: true,
        message: 'Task ID is required.'
      });
    }
    const json = fs.readFileSync(jsonFile);
    let todoList = JSON.parse(json);
    // DELETE
    todoList = todoList.filter((item, index) => {
      return data.id != item.id;
    });
    // SAVE
    fs.writeFileSync(jsonFile, JSON.stringify(todoList));
    return getJSON({
      success: true
    });
  }
}

Good to know

When available use sync processes to perform operations in route handlers.

Then we can use this API to save, update, delete and get a todo list in this way.


// Save New Task
$.post(
  '/examples/api/json', 
  {task: 'New Task'}, 
  (todo) => { 
    console.log(todo); 
  }
);

// Update a Task
$.post(
  '/examples/api/json', 
  {id: 1, task: 'Completed Task', done: true}, 
  (todo) => { 
    console.log(todo); 
  }
);

// Delete a Task
$.ajax(
  '/examples/api/json', 
  {
    method: 'DELETE', 
    data: {id: 1}, 
    success: (todo) => { 
      console.log(todo); 
    }
  }
);

// Get Todo List
$.get(
  '/examples/api/json',
  (todo) => { 
    console.log(todo); 
  }
);


MySQL Database

If you need to access data into a mysql database from Node.js first of all you will need to install a module like mysql.

npm install mysql

Next step is to require the module mysql and then you can use it in your API like this:

let mysql = require("mysql");

function execSQL(sql) {
  return new Promise((resolve, reject) => {
    let conn = mysql.createConnection({
      host: "localhost", 
      user: "root", 
      password: "", 
      database: "test"
    });
    conn.query(sql, function (err, result) {
      if (err) {
        reject(err);
        return;
      }
      resolve(result);
    });
    conn.end();
  });
}

function getTodoList() {
  return execSQL('SELECT * FROM todo;');
}

return {
  GET: async (props) => {
    try {
      let todoList = await getTodoList();
      return getJSON(todoList);
    } catch(error) {
      console.error(error);
      return getJSON({error: 'Unable to get todo list from database.'});
    }
  }
}

Good to know

Working with promises, async and await is sometimes confusing because adds an unnecessary complexity level when processing backend responses, that is the reason we recomend use sync processes when available.

Web Service

To access data from a web service you can use the Node.js HTTPS module to create a request to an API endpoint.

const http = require("https");

function getData() {
  return new Promise((resolve, reject) => {
    http.get('https://rickandmortyapi.com/api/character', (res) => {
      const { statusCode } = res;
      const contentType = res.headers['content-type'];

      let error;
      // Any 2xx status code signals a successful response but
      // here we're only checking for 200.
      if (statusCode !== 200) {
        error = new Error('Request Failed.\n' +
                          `Status Code: ${statusCode}`);
      } else if (!/^application\/json/.test(contentType)) {
        error = new Error('Invalid content-type.\n' +
                          `Expected application/json but received ${contentType}`);
      }
      if (error) {
        console.error(error.message);
        reject(error.message);
        // Consume response data to free up memory
        res.resume();
        return;
      }

      res.setEncoding('utf8');
      let rawData = '';
      res.on('data', (chunk) => { rawData += chunk; });
      res.on('end', () => {
        try {
          const parsedData = JSON.parse(rawData);
          resolve(parsedData);
        } catch (e) {
          console.error(e.message);
          reject(e.message);
        }
     });
    }).on('error', (e) => {
      console.error(`Got error: ${e.message}`);
      reject(e.message);
    });
  });
}

return {
  GET: async (props) => {
    const data = await getData();
    return getJSON({
      apiVersion: '1.0.0',
      apiName: 'The Rick and Morty API',
      data: data
    });
  }
}

In this example we get information of the Rick and Morty Characters using "The Rick and Morty API" endpoint.

Now you are ready to create any App you can imagine.

Good to know

When working with async processes, you will have to manually catch all errors inside the asynchronous functions. If any error is thrown inside an async process without catch the Previous.js server will break down and you will have to start it again.