Creating a CRUD gRPC Service with MongoDB using Node.js: A Comprehensive Guide
To create a gRPC service with CRUD operations backed by MongoDB in Node.js, you'll need to set up the gRPC service, define the Protobuf schema for the operations, and implement the CRUD operations using the MongoDB client.
Below is a comprehensive guide to setting up a basic CRUD gRPC service using Node.js and MongoDB.
Prerequisites
- Node.js: Download and install Node.js from nodejs.org.
- MongoDB: Set up a MongoDB instance. You can use a local instance or MongoDB Atlas for a cloud-hosted MongoDB.
- Protobuf: Download and install the Protocol Buffers compiler (
protoc
) from the Protocol Buffers GitHub page.
Step-by-Step Guide
-
Define the Service Using Protobuf
Create a file named
crud.proto
with the following content:syntax = "proto3"; package example; service CrudService { rpc Create (CreateRequest) returns (CreateResponse); rpc Read (ReadRequest) returns (ReadResponse); rpc Update (UpdateRequest) returns (UpdateResponse); rpc Delete (DeleteRequest) returns (DeleteResponse); } message CreateRequest { string name = 1; string data = 2; } message CreateResponse { string id = 1; } message ReadRequest { string id = 1; } message ReadResponse { string id = 1; string name = 2; string data = 3; } message UpdateRequest { string id = 1; string name = 2; string data = 3; } message UpdateResponse { bool success = 1; } message DeleteRequest { string id = 1; } message DeleteResponse { bool success = 1; }
-
Generate gRPC Code
Use the
protoc
compiler to generate the service code for Node.js:mkdir -p out protoc --js_out=import_style=commonjs,binary:out --grpc_out=out --plugin=protoc-gen-grpc=`which grpc_tools_node_protoc_plugin` crud.proto
This command will generate
out/crud_pb.js
andout/crud_grpc_pb.js
. -
Setup Project
Create a new Node.js project and install the required dependencies:
mkdir grpc_mongo_example cd grpc_mongo_example npm init -y npm install @grpc/grpc-js @grpc/proto-loader mongodb
-
Create Server
Create a file named
server.js
:const grpc = require("@grpc/grpc-js"); const protoLoader = require("@grpc/proto-loader"); const { MongoClient, ObjectId } = require("mongodb"); const PROTO_PATH = "./crud.proto"; // Load protobuf const packageDefinition = protoLoader.loadSync(PROTO_PATH, { keepCase: true, longs: String, enums: String, defaults: true, oneofs: true, }); const crudProto = grpc.loadPackageDefinition(packageDefinition).example; // MongoDB configuration const url = "mongodb://localhost:27017"; const dbName = "grpcdb"; const client = new MongoClient(url, { useNewUrlParser: true, useUnifiedTopology: true }); // Implement CRUD operations const create = async (call, callback) => { try { const db = client.db(dbName); const { name, data } = call.request; const result = await db.collection("items").insertOne({ name, data }); callback(null, { id: result.insertedId.toString() }); } catch (err) { callback(err, null); } }; const read = async (call, callback) => { try { const db = client.db(dbName); const { id } = call.request; const item = await db.collection("items").findOne({ _id: new ObjectId(id) }); if (!item) { callback(new Error("Item not found"), null); } else { callback(null, { id: item._id.toString(), name: item.name, data: item.data }); } } catch (err) { callback(err, null); } }; const update = async (call, callback) => { try { const db = client.db(dbName); const { id, name, data } = call.request; const result = await db.collection("items").updateOne({ _id: new ObjectId(id) }, { $set: { name, data } }); if (result.matchedCount === 0) { callback(new Error("Item not found"), null); } else { callback(null, { success: result.matchedCount > 0 }); } } catch (err) { callback(err, null); } }; const deleteItem = async (call, callback) => { try { const db = client.db(dbName); const { id } = call.request; const result = await db.collection("items").deleteOne({ _id: new ObjectId(id) }); callback(null, { success: result.deletedCount > 0 }); } catch (err) { callback(err, null); } }; // Start the server const main = async () => { await client.connect(); const server = new grpc.Server(); server.addService(crudProto.CrudService.service, { Create: create, Read: read, Update: update, Delete: deleteItem }); server.bindAsync("127.0.0.1:50051", grpc.ServerCredentials.createInsecure(), () => { server.start(); console.log("Server running at http://127.0.0.1:50051"); }); }; main();
-
Create Client
Create a file named
client.js
:const grpc = require("@grpc/grpc-js"); const protoLoader = require("@grpc/proto-loader"); const PROTO_PATH = "./crud.proto"; // Load protobuf const packageDefinition = protoLoader.loadSync(PROTO_PATH, { keepCase: true, longs: String, enums: String, defaults: true, oneofs: true, }); const crudProto = grpc.loadPackageDefinition(packageDefinition).example; const client = new crudProto.CrudService("localhost:50051", grpc.credentials.createInsecure()); // Utility function to print item function printItem(err, response) { if (err) { console.error(err); } else { console.log(response); } } // Create a new item client.Create({ name: "Test Item", data: "Some data" }, (err, response) => { printItem(err, response); // Read the item client.Read({ id: response.id }, (err, response) => { printItem(err, response); // Update the item client.Update({ id: response.id, name: "Updated Item", data: "Updated data" }, (err, response) => { printItem(err, response); // Delete the item client.Delete({ id: response.id }, (err, response) => { printItem(err, response); }); }); }); });
-
Run the Server and Client
First, start the server:
node server.js
Then, in another terminal, run the client:
node client.js
The client will perform the following operations in sequence: create a new item, read the item, update the item, and delete the item, printing the results of each operation to the console.
Conclusion
This example demonstrates how to create a simple CRUD gRPC service backed by MongoDB using Node.js. By defining the service with Protocol Buffers and utilizing gRPC and MongoDB libraries in Node.js, you can build efficient and scalable services that perform CRUD operations. This setup also highlights Node.js’s capability to interoperate with various databases and protocols, making it a versatile choice for backend development.