Build an application with Node.js and PouchDB

While traditional databases like MySQL or PostgreSQL have been the go-to option for storing data in server-side applications, they can be inadequate for applications that require offline-first functionality or real-time synchronization between the server and client.

However, creating a Node.js application with PouchDB allows you to build efficient, scalable, and reliable web and mobile applications that can function seamlessly online and offline. In this article, you will create a simple bookstore API with CRUD (create, read, update, delete) functionality using Node.js, Express.js, and PouchDB.

Jump ahead:

What is PouchDB?

PouchDB is an open source JavaScript database library designed for creating efficient and scalable offline-first web applications. With PouchDB, you can build applications seamlessly across multiple platforms, including browsers, Node.js servers, and mobile devices.

While applications using PouchDB are offline, it stores the data locally. When the applications are online, it synchronizes the data with CouchDB and other compatible servers, keeping the user’s data updated and in sync.

PouchDB provides a lightweight, embedded database that can be easily integrated into applications, allowing users to work offline and synchronize data when a network connection is available. It supports a variety of storage backends, including IndexedDB, LevelDB, and SQLite, and provides a flexible API for querying and manipulating data.

Setting up your development environment

To set up your development environment, create a project directory and cd into it by running the command below:

mkdir pouchdb-tutorial && cd pouchdb-tutorial

Next, create a package.json file with all its defaults by running the command below:

npm init -y

Then, install Express.js by running the command below:

npm install express

After that, install PouchDB by running this command:

npm install pouchdb

Finally, create an index.js file and add the code block below to your file to create a basic Express server:

// index.js
const express = require("express");
const app = express();
const port = 3000;

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.listen(port, () => console.log(`Example app listening on port ${port}!`));

The code block above creates a simple Express server running on port 3000.

Creating a PouchDB database

PouchDB is not a self-contained database; instead, it is an abstraction layer over other databases. By default, when you use PouchDB in the browser, it ships with IndexedDB as an adapter. In Node.js, it ships with LevelDB.

To create a PouchDB database, create a config folder in your project’s root directory and a db.js file in your config folder. Next, add the code block below to your db.js file:

// config/db.js
const pouchDB = require("pouchdb");

// Create a new database instance
const db = new pouchDB("books");

// Export the database instance
module.exports = db;

The code block above created and exported a PouchDB database "books" using the pouchDB constructor. By default, this database’s adapter is LevelDB. Next, import the database instance in your index.js file. Like so:

// index.js
const db = require("./config/db");

Then, add the code block below to your index.js file to get information about your PouchDB database:

// index.js
// Get database info
db.info().then((info) => console.log(info));

The code block above logs information about your database to your console, as shown in the image below:

Implementing CRUD endpoints

Now, let’s implement the CRUD endpoints. Create a routes folder in your project’s root directory and create a book.js file in the folder. Next, add the code block below to your book.js file to implement Express routing:

// book.js
const express = require("express");
const router = express.Router();

PouchDB provides two methods to persist data to a database: post and put. When you save data to your database using put, you must specify and _id property. However, PouchDB automatically generates an _id property when you save data using post. The PouchDB documentation recommends the use of put over post, so this tutorial will cover the put method.

To generate unique _id properties for each document, you will use an npm package, uuid. Run the npm install uuid command below to install the package. Next, import uuid in your book.js file, like so:

// book.js
const { v4: uuidv4 } = require('uuid');

Finally, import your PouchDB database instance in your book.js file:

// book.js
const db = require("../config/db");

Adding a new book to your database

To implement the logic for adding a new book document to your database, add the code block below to your book.js file:

// book.js
// POST /books/new
router.post("/books/new", async (req, res) => {
  const { title, author, genre, year } = req.body;
  // Generating _id
  const _id = uuidv4();

  const book = {
    _id,
    title,
    author,
    genre,
    year,
  };

  // Saving to DB
  db.put(book)
    .then((response) => {
      res.status(201).send(response);
    })
    .catch((error) => {
      res.status(500).json({ error: error.message });
    });
});

The code block above implements a POST route handler for http://localhost:3000/books/new. First, you extracted the required properties from the req.body object. Then, you generated a unique _id by calling the uuidv4 method you imported earlier. Next, you stored the required properties and the _id in an object. Finally, using PouchDB’s asynchronous put method, you store the book object in your database and send a response to the server.

Retrieving books from your database

To implement the logic for getting all the book documents in your database, add the code block below to your book.js file:

// book.js
// GET /books
router.get("/books", async (req, res) => {
  try {
    const books = await db.allDocs({ include_docs: true });
    const response = books.rows.map((book) => book.doc);

    res.status(200).send(response);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

The code block above implements a GET route handler for http://localhost:3000/books/. First, you retrieved all the documents from your database using PouchDB’s allDocs({include_docs: true}) method. The returned document contains a lot of nested data; by accessing the rows property and mapping through it to extract the doc property of each, you’ll get a more readable response that you send back to the server.

To implement the logic for getting a book document based on a given _id in your database, add the code block below to your book.js file:

// book.js
// GET /books/:id
router.get("/books/:id", async (req, res) => {
  try {
    const { id } = req.params;
    const book = await db.get(id);
    res.status(200).send(book);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

The code block above implements a GET route handler for http://localhost:3000/books/:id. First, you extracted the id property from the req.params object. Then, using the extracted id as an argument to the get method, you retrieved the book document with the corresponding _id and sent it back as a response to the server.

Updating existing books in your database

To implement the logic for editing a book document based on a given _id in your database, add the following code block to your book.js file:

// book.js
// PUT /books/:id
router.put("/books/:id", async (req, res) => {
  try {
    const { id } = req.params;
    const { title, author, genre, year } = req.body;

    db.get(id).then(async (doc) => {
      const response = await db.put({
        _id: id,
        _rev: doc._rev,
        title,
        author,
        genre,
        year,
      });
      res.status(201).send(response);
    });
  } catch (error) {
    console.log(error);
    res.status(500).json({ error: error.message });
  }
});

The code block above implements a PUT route handler for http://localhost:3000/books/:id. First, you extracted the id property from the req.params object. Next, you extracted the required properties from the req.body object. Then, using the extracted id as an argument to the get method, you retrieved the book document with the corresponding _id and replaced the old properties with the extracted properties.

Notice that a _rev property was passed along with the extracted properties into the put method. The _rev property ensures that the syncing process happens correctly by preventing possible conflicts when the application is online.

Deleting a book from your database

Now, to implement the logic for deleting a book document based on a given _id in your database, add the following code to your book.js file.

// book.js
// DELETE /books/:id
router.delete("/books/:id", async (req, res) => {
  try {
    const id = req.params.id;
    const doc = await db.get(id);
    const response = await db.remove(doc);
    res.status(200).send(response);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

The code block above implements a DELETE route handler for http://localhost:3000/books/:id. First, you extracted the id property from the req.params object. Then, using the extracted id as an argument to the get method, you retrieved the book document with the corresponding _id and passed it as an argument to PouchDB’s remove method, effectively deleting it from the database.

Export your Express router by adding the code block below to your book.js file:

// book.js
module.exports = router;

Then, import your Express router in your index.js file and use it as middleware:

// index.js
app.use(bookRouter);

Finally, you can start up your application by running the command below:

node index.js

Conclusion

In this article, you built a functional Node.js API with Express.js using PouchDB as your database. As a server-side application with PouchDB, you used LevelDB as your database adapter. You can learn more about PouchDB in the official PouchDB documentation.

200’s only Monitor failed and slow network requests in production

Deploying a Node-based web app or website is the easy part. Making sure your Node instance continues to serve resources to your app is where things get tougher. If you’re interested in ensuring requests to the backend or third party services are successful, try LogRocket. https://logrocket.com/signup/

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens while a user interacts with your app. Instead of guessing why problems happen, you can aggregate and report on problematic network requests to quickly understand the root cause.

LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. .


Source link