Build a CRUD App With Only JSON Files Using a Node.js API

by Uma Victor

10 min read

When building a small web app or just testing out an API that needs to perform CRUD operations, using a database like (Mongo, Mysql) can be overkill. In this tutorial, we will learn how we can create CRUD operations with just JSON files.

Goals

In this tutorial, we will learn how to serve and edit data from just our JSON file and how to post and save data in our JSON file. All that is needed to follow along in this tutorial is a little bit of Node.js development knowledge.

Overview

In this tutorial, we'll go straight to the point of creating and setting up our server and all the endpoints that we need. We'll then build all our routes and test the app, creating new accounts, and making CRUD operations.

Prerequisite

To follow along in this tutorial, you don’t need to be a “badass” programmer – a general programming knowledge, a little bit of Javascript, and familiarity with Node.js will suffice.

Kickstarting Our Project

First, we'll create a project folder with the name of the project and initialize the package.json file containing information about our project and all the dependencies we're going to need.

mkdir crud-operation-with-jsonfile
cd crud-operation-with-jsonfile
npm init -y
code .

There are some dependencies that are necessary for building this project, including:

  • Express - a Node.js framework for easily performing HTTP request and creating API’s.
npm install express
  • Nodemon - a very helpful package that will automatically restart your server once there is any file change.
npm install nodemon
  • Body-parser - a body parsing middleware used for parsing and processing data from the body sent to our express server.
npm install body-parser

Setting Up the Server

Now that we have everything we need to start building, we have created an app.js file at the root of our project folder. This is where we'll begin building our server.

Note: Set app.js file as the project entry file in our package.json and nodemon to target the app.js file and automatically restart the server once there's a change.

 "main": "app.js",
  "scripts": {
    "start": "nodemon app.js"
  },

In the app.js file, we require express, body-parser and Node.js built in file system (fs) module which we will be using to serve our JSON file.

const express = require("express")
const bodyParser = require("body-parser")
const fs = require('fs');
// create our express app
const app = express()
// middleware
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }));
// route
const routes = require('./routes/Routes')
app.use('/', routes)
//start server
app.listen(3000, ()=>{
    console.log("listeniing at port:3000")
}) 

 There are a few things going on in the app.js file above:

  • We created and initialized our express application.
  • We set up body-parser to handle JSON data from our body to the express API we create.
  • We imported/required the file that we make all our routing operations in.
  • We started our server and listened for requests at port 3000.

If we try running this app.js file, we will run into an error because we haven’t set up our routes. That is exactly what we'll do next.

Setting Up Our Routes

We want the first route we created in this application to be the account route. To do this, we create a route folder at the root of the directory, and in this directory, we create a Route.js file. Back in our app.js file above, we can see that we have already imported the route. All our requests will now be prepended with /  now that we have created the routes.

In the Route.js file, we require express and the fs module again and define our express router. We also make sure to export the router.

// Route.js
const express = require("express")
const router = express.Router();
const fs = require('fs');


module.exports = router;

 Account Route

Create a details folder at the root of the application and add an account.json file, which is a JSON file containing user account details. 

{
    "1465": {
        "username": "madfinger11",
        "email": "[email protected]",
        "password": "yddam5342"
    },
    "2646": {
        "username": "yemiakin",
        "email": "[email protected]",
        "password": "iy@g65"
    },
    "3253": {
        "username": "ikechifortune",
        "email": "[email protected]",
        "password": "ifo#mf23"
    }
}

Now that our JSON data is available to us, let us begin performing the CRUD operations.

In the same Route folder, we create an account.js file that serves as our account route. This is the route where we perform our CRUD operations. We define our router with the accountRoutes variable and we export the route.

// account.js
const express = require("express")
const accountRoutes = express.Router();
const fs = require('fs');

module.exports = accountRoutes

For our server to know this route exists, we have to import it into the main Route.js file and use it.

// Route.js file
const express = require("express")
const router = express.Router();
const fs = require('fs');
const accountRoutes = require('./account.js') // import account route
router.use(accountRoutes) // use account route
module.exports = router;

While we perform CRUD operations, there'll be times when we'll need to use the file system to read and write data to our JSON file. We can make our code a bit cleaner from the start by declaring util functions at the top of the file with the main purpose of reading and writing from our file system.

For now, all we'll be doing will take place within the account route.

The two util functions we'll be reusing are:

  • saveAccountData - This function will make use of the writeFileSync method to read our account data in the JSON file.
  • getAccountData - This function will make use of the readFileSync method to get the account data from the JSON file.
const dataPath = './Details/useraccount.json' // path to our JSON file

// util functions
const saveAccountData = (data) => {
    const stringifyData = JSON.stringify(data)
    fs.writeFileSync(dataPath, stringifyData)
}
const getAccountData = () => {
    const jsonData = fs.readFileSync(dataPath)
    return JSON.parse(jsonData)   
}

Create an Account

Each Account in the JSON database should have a unique id to identify each account easily. We will generate a 6 digit number using Javascript math.random, then assign the 6 digit number to a variable newAccountId and set the new Id to whatever we get from our body. Our JSON data is now updated with the latest information of the new account created. We'll use the saveAccountData util function to write the new data into our JSON file.

Lastly, we'll send a success message:

accountRoutes.post('/account/addaccount', (req, res) => {
 
    var existAccounts = getAccountData()
    const newAccountId = Math.floor(100000 + Math.random() * 900000)
 
    existAccounts[newAccountId] = req.body
   
    console.log(existAccounts);
    saveAccountData(existAccounts);
    res.send({success: true, msg: 'account added successfully'})
})

Reading Data

Now that we have implemented being able to create a user, we want to be able to fetch all the available accounts data we have in our JSON file and display it. We'll use the get method to do this.

When we hit the /account/list endpoint, we then run the getAccountData util function, store it in a variable accounts, and send the JSON data.

// Read - get all accounts from the json file
accountRoutes.get('/account/list', (req, res) => {
  const accounts = getAccountData()
  res.send(accounts)
})

Updating Account Data

To update a particular piece of account information, we have to set up a dynamic route /account/:id. The /account/list is going to be whatever id of the property we choose to update in our account data.

When we hit the endpoint, we'll fetch the account data using the getAccountData util function and store it in the existAccount variable. We'll get the id from the route and use it to target the account data we want to change in our list of account data, setting it to whatever new data we send from our body.

We'll save the updated list of account data using the saveAccountData util function and send a success message saying that the id has been updated successfully.

// Update - using Put method
accountRoutes.put('/account/:id', (req, res) => {
  var existAccounts = getAccountData()
  fs.readFile(dataPath, 'utf8', (err, data) => {
    const accountId = req.params['id'];
    existAccounts[accountId] = req.body;
    saveAccountData(existAccounts);
    res.send(`accounts with id ${accountId} has been updated`)
  }, true);
});

Delete an Account

In the update method we used above, we targeted the ID we wanted to update. The same thing happens when we want to delete an account.

First, we'll get the list of existing account data and then we'll get the id from our dynamic endpoint route /account/delete/:id using req.params. We'll target the id in the account list data we fetched and use the Javascript delete operator used in deleting properties from an object to delete that particular property with the associated id. A message regarding the account with that id being deleted will be sent.

// delete - using delete method
accountRoutes.delete('/account/delete/:id', (req, res) => {
  fs.readFile(dataPath, 'utf8', (err, data) => {
    var existAccounts = getAccountData()
    const userId = req.params['id'];
    delete existAccounts[userId]; 
    saveAccountData(existAccounts);
    res.send(`accounts with id ${userId} has been deleted`)
  }, true);
})

Testing Our App

We are now all done with the crud operations. The next step is to test it out using Postman, which is a platform we can use to test if our API is working. 

To test our READ operation, we'll open Postman and go to http://localhost:3000/account/list endpoint and it will return all the account data:

To test the POST operation, we'll set the new account info in the body of our request. When we send the request, we'll notice that a new account object has been added in our JSON file and a success message has been displayed in Postman.

To test the UPDATE operation, we'll choose the Id of the account we want to update. The next step is to add the new data object and send the request. The JSON data is now updated and we get the success message with the ID of the data that was updated.

To test the DELETE operation, we'll set the ID of the account we want to delete. When we navigate to the endpoint /account/delete/:id and send our request, the JSON data will be updated and our account will no longer exists. We'll also get a message on Postman stating the ID of the account that was deleted.

Note: We need to know that using a JSON file as a database is not particularly a good idea for real-world applications. This tutorial teaches you how to perform a normal operation that a database like mongo is capable of if you're working on a little project and don’t want to face the hassle of setting up a native database.

Conclusion

Yay! We have come to the end of this tutorial.

Hopefully, we have learned how to use a JSON file as a database and perform CRUD operations on it.

The code for this project can be found in this Github repo.

And if you're eager to enhance your programming skills, add these blog posts to your reading list:

FAQs

Q: How to prioritize tasks in a queue?
Implement task prioritization in a queue by using a priority system where each task is assigned a priority level. Tasks with higher priority levels are processed before those with lower priorities. 
Q: Can queues be used for rate limiting? How?
Yes, queues can be used for rate limiting by controlling the number of tasks processed within a given timeframe. This regulates the task execution rate to prevent system overload.
Q: What are the failure recovery strategies for queue processing?
Use retry mechanisms for temporary errors, dead-letter queues for unprocessable messages, and alerting for monitoring failures. Implement idempotency to safely reprocess tasks without side effects.
Uma Victor
Uma Victor
Software Engineer, Technical Writer

Uma is a software developer based in Nigeria who is familiar with a variety of different web technologies and frameworks. He is also keen on finding ways to explain things as simple as possible.

Expertise
  • Node.js
  • JavaScript
  • ReactJS
  • TypeScript
  • MongoDB

Ready to start?

Get in touch or schedule a call.