Using Node.js and PouchDB to build an application

Build an Application With Node.js and PouchDB

MySQL or PostgreSQL are two commonly used databases to store data in server-side applications, however in many cases they may not be sufficient for applications that require offline priority functionality. or real-time synchronization between server and client.

However, creating Node.js applications with PouchDB allows you to build efficient, scalable, and reliable web and mobile applications that can work seamlessly online and offline. Let's create a simple bookstore API with CRUD (create, read, update, delete) functionality using Node.js, Express.js and PouchDB.

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:

Database for Node.js and PouchDB

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.

No comments:

Post a Comment