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:
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