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:
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.
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
.
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:
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");
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.
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.
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.
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
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.
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.
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. Start monitoring for free.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowCompare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.