course
शुरुआत में एक साधारण टू-डू लिस्ट बढ़िया काम करती है: आप कुछ टास्क जोड़ते हैं और पूरा होने के बाद उन्हें बंद कर देते हैं। लेकिन जैसे-जैसे टास्क बढ़ते जाते हैं, इसका इस्तेमाल करना मुश्किल हो जाता है। इसे सुधारने का एक तरीका है कैटेगराइज़ेशन जोड़ना, जिससे हर टास्क Work, Study, Personal आदि जैसी कैटेगरी में आएगा, और आप सूची को संबंधित कैटेगरी के हिसाब से फ़िल्टर कर सकेंगे।
इस ट्यूटोरियल में, आप सीखेंगे कि Node.js और MongoDB का उपयोग करके कैटेगरी के आधार पर टास्क व्यवस्थित करने वाली एक REST API कैसे बनाएं। अंत तक, आपके पास पाँच एंडपॉइंट्स—create, list, get, update, और delete—के साथ-साथ एक कैटेगरी फ़िल्टर वाला काम करता हुआ Node.js सर्वर होगा। आप यह भी देखेंगे कि Mongoose जैसे ODM पर निर्भर किए बिना, नेटिव MongoDB ड्राइवर का उपयोग करके कैटेगरी वैलिडेशन कैसे लागू करें।
आप क्या सीखेंगे
- Node.js, Express और MongoDB के साथ REST API कैसे बनाएं, जिसमें फुल CRUD एंडपॉइंट्स शामिल हों
- इनपुट को कैसे वैलिडेट करें, कैटेगरीज़ को कैसे लागू करें, और ObjectId मानों को सुरक्षित रूप से कैसे संभालें
- अपनी API को कैसे संरचित करें और टेस्ट करें, फिर उसे फ्रंटएंड से कैसे जोड़ें
यदि आप चाहें, तो इस ट्यूटोरियल का पूरा कोड GitHub पर मिल जाएगा—आप उसे क्लोन करके साथ-साथ पढ़ सकते हैं।
पूर्व-आवश्यकताएँ
शुरू करने से पहले, आपके पास यह होना चाहिए:
- Node.js 18+ इंस्टॉल (npm डिफ़ॉल्ट रूप से शामिल होता है)
- स्थानीय रूप से चल रहा MongoDB, या एक फ्री MongoDB Atlas क्लस्टर
- Postman या कोई भी HTTP क्लाइंट
- JavaScript और REST अवधारणाओं का बुनियादी ज्ञान
स्टेप 1: प्रोजेक्ट सेट करें
आइए अपने सरल टास्क मैनेजर का निर्माण शुरू करें। सबसे पहले, हम एक नया फ़ोल्डर बनाएंगे और नीचे दिए गए कमांड चलाकर एक Node प्रोजेक्ट इनिशियलाइज़ करेंगे:
mkdir task-manager-categories
cd task-manager-categories
npm init -y
इसके बाद, हम चार रनटाइम डिपेंडेंसी इंस्टॉल करेंगे जिनकी हमारे ऐप को ज़रूरत है: रूटिंग के लिए express, आधिकारिक MongoDB ड्राइवर, एनवायरनमेंट वेरिएबल्स लोड करने के लिए dotenv, और अंत में सिक्योरिटी हेडर के लिए helmet।
npm install express mongodb dotenv helmet
डेवलपमेंट के लिए, नीचे दिए गए कमांड से nodemon को dev dependency के रूप में इंस्टॉल करें ताकि फाइलों में बदलाव पर सर्वर अपने-आप रीलोड हो जाए:
npm install --save-dev nodemon
अब अपने IDE में प्रोजेक्ट फ़ोल्डर खोलें और scripts सेक्शन को package.json में नीचे दिए अनुसार अपडेट करें। इससे आप npm start से सर्वर शुरू कर पाएंगे या डेवलपमेंट मोड में npm run dev चला पाएंगे:
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
}
यहाँ वह स्ट्रक्चर है जिसे हम बनाते चलेंगे। हर फ़ोल्डर की स्पष्ट भूमिका है: db/ डेटाबेस कनेक्शन संभालता है, lib/ में हेल्पर फ़ंक्शंस होते हैं, middleware/ Express मिडलवेयर के लिए है, और routes/ रिक्वेस्ट हैंडलर परिभाषित करता है। public/ फ़ोल्डर में बाद में जोड़ा जाने वाला एक छोटा फ्रंटएंड होगा। छोटे प्रोजेक्ट में भी यह संरचना चीज़ों को समझना आसान बनाती है और स्पष्ट करती है कि नया कोड कहाँ जाना चाहिए। चाहें तो अभी फ़ोल्डर लेआउट बना लें और आगे बढ़ते हुए भरें, या इसे छोड़ दें और हर स्टेप पर फाइलें जोड़ते जाएँ:
task-manager-categories/
├── db/
│ └── connect.js
├── lib/
│ └── taskDocument.js
├── middleware/
│ └── parseObjectId.js
├── routes/
│ └── tasks.js
├── public/
│ ├── index.html
│ ├── styles.css
│ └── app.js
├── .env
├── .env.example
├── .gitignore
├── package.json
└── server.js
स्टेप 2: MongoDB से कनेक्ट करें
टास्क को स्टोर और रिट्रीव करने के लिए, हमें सबसे पहले अपने ऐप को MongoDB से कनेक्ट करना होगा। यह एक कनेक्शन स्ट्रिंग के जरिए किया जाता है, जो MongoDB ड्राइवर को बताता है कि आपके डेटाबेस से कैसे कनेक्ट होना है। इसे सीधे कोड में हार्डकोड करने के बजाय, इसे एनवायरनमेंट फाइल में रखना बेहतर है। इससे संवेदनशील मान आपके कोडबेस से बाहर रहते हैं और एनवायरनमेंट बदलना आसान हो जाता है।
आवश्यक वेरिएबल्स डॉक्यूमेंट करने के लिए एक .env.example फाइल बनाएं, और वास्तविक मानों के लिए एक .env फाइल, इस तरह:
# Copy this file to `.env` and fill in real values.
# --- Local MongoDB ---
# Use this if you're running MongoDB locally
MONGO_URI=mongodb://127.0.0.1:27017
# --- MongoDB Atlas ---
# Replace <username>, <password>, and <cluster-url> with your actual values
# Example: mongodb+srv://user:pass@cluster0.abcde.mongodb.net/
# MONGO_URI=mongodb+srv://<username>:<password>@<cluster-url>/?retryWrites=true&w=majority
DB_NAME=taskmanager
PORT=3000
अब जब हमारे पास कनेक्शन स्ट्रिंग है, तो हम इसका उपयोग करके अपना MongoDB कनेक्शन सेट कर सकते हैं। एक db/connect.js फाइल बनाएं। यहीं हम MongoDB क्लाइंट इनिशियलाइज़ करेंगे और उसे ऐप के बाकी हिस्सों के लिए उपलब्ध कराएँगे:
const { MongoClient } = require("mongodb");
let client;
let db;
async function connectDB() {
if (!process.env.MONGO_URI) {
throw new Error("MONGO_URI is not set. Check your .env file.");
}
client = new MongoClient(process.env.MONGO_URI, {
appName: "devrel-tutorial-javascript-crud-geeksforgeeks",
});
await client.connect();
db = client.db(process.env.DB_NAME || "taskmanager");
await db.collection("tasks").createIndex({ category: 1 });
console.log(`MongoDB connected (db: ${db.databaseName})`);
}
function getTasksCollection() {
if (!db) {
throw new Error("Database not initialized. Call connectDB() first.");
}
return db.collection("tasks");
}
async function closeDB() {
if (client) await client.close();
}
module.exports = { connectDB, getTasksCollection, closeDB };
यह फाइल एक सिंगल MongoDB क्लाइंट सेट करती है जिसे ऐप के बाकी हिस्से दोबारा उपयोग कर सकते हैं। आप इसे केवल एक बार बनाना चाहेंगे, क्योंकि ड्राइवर अंदर ही अंदर कनेक्शन पूलिंग संभालता है। हर रिक्वेस्ट पर नया क्लाइंट बनाना शुरुआत में ठीक लग सकता है, लेकिन यह जल्दी ही परफॉर्मेंस समस्याएँ पैदा करेगा।
स्टेप 3: टास्क की संरचना और वैलिडेशन परिभाषित करें
इस बिंदु पर, ऐप MongoDB से कनेक्ट हो सकता है। अब हमें यह परिभाषित करना है कि किसी टास्क का वास्तविक रूप कैसा होगा, इससे पहले कि हम कुछ भी सेव करें।
यहीं नेटिव MongoDB ड्राइवर का उपयोग थोड़ा अलग महसूस होने लगता है। यहाँ Mongoose की तरह कोई स्कीमा फाइल नहीं होती। इसके बजाय, "स्कीमा" वही ऑब्जेक्ट संरचना है जिसे आप डेटाबेस में इंसर्ट करते हैं। शुरुआत में यह ढीला-ढाला लग सकता है, लेकिन यह वास्तव में मददगार है क्योंकि आप MongoDB के असली व्यवहार के क़रीब रहते हैं, और कुछ भी एब्स्ट्रैक्शन के पीछे नहीं छिपता।
फिर भी हमें वैलिडेशन चाहिए। अलग-अलग रूट्स में लॉजिक बिखेरने के बजाय, हम इसे एक ही जगह रखेंगे ताकि सब कुछ एक ही नियमों का पालन करे। एक lib/taskDocument.js नाम की फाइल बनाएँ और इसमें नीचे दिया गया कोड जोड़ें:
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = 'ValidationError';
}
}
const ALLOWED_CATEGORIES = ['Work', 'Personal', 'Study', 'Other'];
function assertValidCategory(category) {
if (!ALLOWED_CATEGORIES.includes(category)) {
throw new ValidationError(
`category must be one of: ${ALLOWED_CATEGORIES.join(', ')}`
);
}
}
function buildTaskDocument(body = {}) {
if (!body.title || typeof body.title !== 'string' || !body.title.trim()) {
throw new ValidationError('title is required and must be a non-empty string');
}
if (body.category != null) {
assertValidCategory(body.category);
}
const now = new Date();
return {
title: body.title.trim(),
description: typeof body.description === 'string' ? body.description.trim() : '',
category: body.category != null ? body.category : 'Other',
completed: Boolean(body.completed),
createdAt: now,
updatedAt: now
};
}
function buildTaskUpdate(body = {}) {
const updates = {};
if (typeof body.title === 'string' && body.title.trim()) {
updates.title = body.title.trim();
}
if (typeof body.description === 'string') {
updates.description = body.description.trim();
}
if (body.category != null) {
assertValidCategory(body.category);
updates.category = body.category;
}
if (body.completed != null) {
updates.completed = Boolean(body.completed);
}
if (Object.keys(updates).length === 0) {
throw new ValidationError('no valid fields provided for update');
}
updates.updatedAt = new Date();
return updates;
}
module.exports = {
ALLOWED_CATEGORIES,
buildTaskDocument,
buildTaskUpdate,
ValidationError
};
यह फाइल आपके डेटाबेस में जाने वाली हर चीज़ के लिए गेटकीपर की तरह काम करती है। हर create या update रिक्वेस्ट यहाँ से होकर गुजरती है, इसलिए नियम एक ही जगह से लागू होते हैं।
यह सुनिश्चित करती है कि टास्क हमेशा सही रूप में हों, कैटेगरीज़ सुसंगत रहें, और जब आप डेटा क्वेरी करना शुरू करें तो कोई आश्चर्य न हो। कस्टम ValidationError आपको ख़राब इनपुट और वास्तविक सर्वर समस्याओं को अलग करने का साफ़ तरीका देता है, ताकि आपकी API उचित प्रतिक्रिया दे सके। यह सेटअप होने के बाद, ऐप का बाकी हिस्सा सरल रह सकता है। हर रूट अपना काम कर सकता है, यह जानते हुए कि उसे पहले से वैलिड डेटा मिल रहा है। अगला कदम है रूट्स को वायर करना और MongoDB में टास्क सेव करना शुरू करना।
स्टेप 4: ObjectId और रिक्वेस्ट वैलिडेशन संभालें
अब जब हमें पता है कि एक टास्क कैसा दिखता है, तो आइए देखें कि हम उसे कैसे रेफरेंस करते हैं। जब भी किसी रूट में :id पैरामीटर शामिल होता है, वह एक साधारण स्ट्रिंग के रूप में आता है। लेकिन MongoDB को एक ObjectId की उम्मीद होती है। यदि वह स्ट्रिंग खराब फॉर्मेट में है, तो ड्राइवर एक कम उपयोगी त्रुटि फेंकता है। हर रूट में इसे संभालने के बजाय, हम इसे मिडलवेयर के जरिए केंद्रीकृत करेंगे ताकि हर एंडपॉइंट एक जैसा व्यवहार करे।
इसके लिए, एक middleware/parseObjectId.js नाम की फाइल बनाएँ और इसमें निम्नलिखित जोड़ें:
const { ObjectId } = require('mongodb');
function parseObjectId(req, res, next) {
const { id } = req.params;
if (!ObjectId.isValid(id)) {
return res.status(400).json({ error: 'invalid task id' });
}
req.taskId = new ObjectId(id);
next();
}
module.exports = parseObjectId;
यह मिडलवेयर आपके रूट हैंडलर से पहले चलता है। यह जाँचता है कि id वैध है या नहीं, उसे एक ObjectId में बदलता है, और उसे req.taskId में अटैच कर देता है। इस तरह, जब तक आपका रूट लॉजिक चलता है, तब तक आप हमेशा एक सही ObjectId के साथ काम कर रहे होते हैं, और गलत इनपुट को शुरुआती चरण में ही स्पष्ट 400 प्रतिक्रिया के साथ अस्वीकार कर दिया जाता है। अब हम इसे अपने रूट्स में जोड़ेंगे और सब कुछ वायर करना शुरू करेंगे।
स्टेप 5: टास्क रूट्स बनाएं (CRUD + फ़िल्टरिंग)
इस बिंदु पर, ज़्यादातर भारी काम हो चुका है। हमने परिभाषित कर लिया है कि एक वैध टास्क कैसा दिखता है, और हमने IDs को पार्स और वैलिडेट करना संभाल लिया है। इसका मतलब है कि हमारे रूट हैंडलर एक ही चीज़ पर केंद्रित रह सकते हैं: डेटाबेस से बात करना।
अब वास्तविक API रूट्स बनाते हैं—एक routes/tasks.js फाइल बनाकर इसमें निम्नलिखित जोड़ें:
const express = require('express');
const { getTasksCollection } = require('../db/connect');
const {
ALLOWED_CATEGORIES,
buildTaskDocument,
buildTaskUpdate,
ValidationError
} = require('../lib/taskDocument');
const parseObjectId = require('../middleware/parseObjectId');
const router = express.Router();
// POST /tasks
router.post('/', async (req, res, next) => {
try {
const doc = buildTaskDocument(req.body);
const result = await getTasksCollection().insertOne(doc);
res.status(201).json({ _id: result.insertedId, ...doc });
} catch (err) {
if (err instanceof ValidationError) {
return res.status(400).json({ error: err.message });
}
next(err);
}
});
// GET /tasks (optionally ?category=Work)
router.get('/', async (req, res, next) => {
try {
const { category } = req.query;
if (category && !ALLOWED_CATEGORIES.includes(category)) {
return res.status(400).json({
error: `category must be one of: ${ALLOWED_CATEGORIES.join(', ')}`
});
}
const filter = category ? { category } : {};
const tasks = await getTasksCollection()
.find(filter)
.sort({ createdAt: -1 })
.toArray();
res.json(tasks);
} catch (err) { next(err); }
});
// GET /tasks/:id
router.get('/:id', parseObjectId, async (req, res, next) => {
try {
const task = await getTasksCollection().findOne({ _id: req.taskId });
if (!task) return res.status(404).json({ error: 'task not found' });
res.json(task);
} catch (err) { next(err); }
});
// PUT /tasks/:id
router.put('/:id', parseObjectId, async (req, res, next) => {
try {
const updates = buildTaskUpdate(req.body);
const result = await getTasksCollection().findOneAndUpdate(
{ _id: req.taskId },
{ $set: updates },
{ returnDocument: 'after' }
);
if (!result) return res.status(404).json({ error: 'task not found' });
res.json(result);
} catch (err) {
if (err instanceof ValidationError) {
return res.status(400).json({ error: err.message });
}
next(err);
}
});
// DELETE /tasks/:id
router.delete('/:id', parseObjectId, async (req, res, next) => {
try {
const result = await getTasksCollection().deleteOne({ _id: req.taskId });
if (result.deletedCount === 0) {
return res.status(404).json({ error: 'task not found' });
}
res.status(204).end();
} catch (err) { next(err); }
});
module.exports = router;
ऊपर के कोड में हर रूट एक ही पैटर्न का पालन करता है: रिक्वेस्ट से इनपुट लेना, उसे हमारे पहले बनाए हेल्पर्स से गुज़रना, MongoDB को कॉल करना, और फिर प्रतिक्रिया लौटाना। वैलिडेशन और ID पार्सिंग पहले से संभाल लेने के कारण, यहाँ का कोड छोटा और अनुमानित रहता है। व्यवहार में, यह ऐसे काम करता है:
- POST रूट buildTaskDocument का उपयोग करके नया टास्क बनाता है, फिर उसे इंसर्ट करता है
- GET रूट्स वैकल्पिक रूप से कैटेगरी के आधार पर फ़िल्टर करते हैं और नये-से-पुराने क्रम में परिणाम लौटाते हैं
- PUT रूट buildTaskUpdate का उपयोग करता है, जिससे आंशिक अपडेट सुरक्षित और सुसंगत रहते हैं
- DELETE रूट पहले से पार्स किए गए ObjectId के जरिए टास्क हटाता है
इस कोड में कुछ और बातें भी ध्यान देने लायक हैं:
- find() एक कर्सर लौटाता है, ऐरे नहीं, इसलिए वास्तविक परिणाम पाने के लिए हम सॉर्टिंग के बाद .toArray() चेन करते हैं
- findOneAndUpdate में returnDocument: 'after' देने से अपडेटेड डॉक्युमेंट तुरंत मिल जाता है
- हम सही HTTP स्टेटस कोड लौटाते हैं: क्रिएट के लिए 201, डिलीट के लिए 204, और आवश्यकतानुसार 400 या 404
- कोई भी अप्रत्याशित त्रुटि next(err) को पास कर दी जाती है, ताकि उन्हें हर रूट के भीतर संभालने के बजाय एक ही जगह संभाला जा सके
स्टेप 6: सर्वर में सब कुछ वायर करें
अब तक, सभी हिस्से तैयार हैं। हमारे पास वैलिडेशन है, साफ़ रूट्स हैं, और काम करता हुआ डेटाबेस कनेक्शन है। अब हमें सब कुछ जोड़ना है और सर्वर को वास्तव में शुरू करना है। एक server.js फाइल बनाएं। यह ऐप का एंट्री पॉइंट है, जहाँ सब कुछ एक साथ आता है:
require('dotenv').config();
const path = require('path');
const express = require('express');
const helmet = require('helmet');
const { connectDB, closeDB } = require('./db/connect');
const tasksRouter = require('./routes/tasks');
if (!process.env.MONGO_URI) {
console.error('Fatal: MONGO_URI is not set. Copy .env.example to .env and fill it in.');
process.exit(1);
}
const PORT = Number(process.env.PORT) || 3000;
const app = express();
app.use(helmet());
app.use(express.json({ limit: '100kb' }));
app.use(express.static(path.join(__dirname, 'public')));
app.get('/health', (req, res) => {
res.json({ status: 'ok', service: 'task-manager-with-categories' });
});
app.use('/tasks', tasksRouter);
app.use((req, res) => {
res.status(404).json({ error: 'not found' });
});
app.use((err, req, res, next) => {
if (err.type === 'entity.too.large') {
return res.status(413).json({ error: 'payload too large' });
}
if (err.type === 'entity.parse.failed') {
return res.status(400).json({ error: 'invalid JSON body' });
}
console.error(err);
res.status(500).json({ error: 'internal server error' });
});
async function start() {
try {
await connectDB();
const server = app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
const shutdown = () => {
console.log('\nShutting down gracefully...');
server.close(async () => {
await closeDB();
process.exit(0);
});
setTimeout(() => process.exit(1), 10_000).unref();
};
process.on('SIGINT', shutdown);
process.on('SIGTERM', shutdown);
} catch (err) {
console.error('Failed to start server:', err);
process.exit(1);
}
}
if (require.main === module) {
start();
}
module.exports = { app, start };
यह फाइल सब कुछ साफ़ तरीके से जोड़ती है। यह कुछ भी चलने से पहले जाँचती है कि आपके एनवायरनमेंट वेरिएबल्स सेट हैं, helmet के साथ बेसिक सिक्योरिटी लागू करती है, और इनकमिंग JSON को लिमिट करती है ताकि आपका ऐप गलती से बहुत बड़े पेलोड स्वीकार न कर ले। यह आपके public/ फ़ोल्डर को भी सर्व करता है, इसलिए आपका फ्रंटएंड इसी ऐप में बिना अलग सर्वर के चल सकता है।
आपके सभी रूट्स /tasks के तहत माउंटेड हैं, और जो कुछ मेल नहीं खाता उसे एक साफ़ 404 मिलता है। एरर्स को एक ही जगह हैंडल किया जाता है, जिससे चीज़ें सुसंगत और रखरखाव में आसान रहती हैं। सर्वर के सुनना शुरू करने से पहले डेटाबेस कनेक्शन स्थापित हो जाता है, इसलिए आप तब तक रिक्वेस्ट स्वीकार नहीं करते जब तक आप तैयार न हों, और शटडाउन भी ग्रेसफुली होता है ताकि कनेक्शन सही तरीके से बंद हो सकें।
अब npm run dev चलाकर इसे शुरू करें और सुनिश्चित करें कि सब कुछ काम कर रहा है। यदि सब कुछ सही से वायर है, तो आपको कुछ ऐसा दिखेगा:
MongoDB connected (db: taskmanager)
Server running on http://localhost:3000
इस समय, सब कुछ वायर है और चल रहा है! अब हमारे पास पूरी तरह काम करती API है, तो आइए इसे टेस्ट करते हैं।
स्टेप 7: API एंडपॉइंट्स का परीक्षण करें
UI बनाने से पहले, यह सुनिश्चित करना उपयोगी है कि हर एंडपॉइंट अपने आप में काम करता है। इससे डिबगिंग आसान हो जाती है, क्योंकि आप जल्दी जान सकते हैं कि समस्या API से आ रही है या फ्रंटएंड से। हम Postman (या कोई भी HTTP क्लाइंट) का उपयोग करेंगे और कुछ रिक्वेस्ट क्रम से चलाएँगे। हर रिक्वेस्ट पिछले स्टेप में बने डेटा पर आधारित होगी।
टास्क बनाएं: एक POST रिक्वेस्ट भेजें जिसमें टाइटल, डिस्क्रिप्शन और कैटेगरी हो। यदि सब कुछ ठीक है, तो API नए टास्क और उसके जेनरेटेड _id के साथ 201 Created लौटाती है।
POST http://localhost:3000/tasks
Content-Type: application/json
{
"title": "Write GeeksForGeeks article",
"description": "First draft by Friday",
"category": "Work"
}
कुछ और बनाएँ: वही POST रिक्वेस्ट अलग-अलग बॉडी के साथ चलाएँ ताकि लिस्टिंग और फ़िल्टरिंग टेस्ट करने के लिए पर्याप्त डेटा हो। इसके बाद, आपके पास क्वेरी करने के लिए एक छोटा डेटा सेट होगा।
{ "title": "Go for a run", "category": "Personal" }
{ "title": "Read MongoDB docs", "category": "Study" }
{ "title": "Buy groceries" } // category defaults to "Other"
सभी टास्क सूचीबद्ध करें: यह सभी टास्क लौटाता है, नये-से-पुराने क्रम में।
GET http://localhost:3000/tasks
कैटेगरी के आधार पर फ़िल्टर करें: यह केवल Work टास्क लौटाता है। पहले जो इंडेक्स जोड़ा था, वही आपके डेटा बढ़ने पर भी इस क्वेरी को तेज़ रखता है।
GET http://localhost:3000/tasks?category=Work
टास्क अपडेट करें: उन टास्क में से किसी एक का _id लें जिसे आपने अभी बनाया है (आप इसे POST प्रतिक्रिया या लिस्ट एंडपॉइंट से कॉपी कर सकते हैं)। यह अपडेटेड टास्क लौटाता है। आपको केवल वे ही फ़ील्ड भेजने हैं जिन्हें आप बदलना चाहते हैं, इसलिए आंशिक अपडेट सरल रहते हैं।
PUT http://localhost:3000/tasks/<paste-task-id-here>
Content-Type: application/json
{ "completed": true }
टास्क हटाएँ: जिस टास्क को हटाना है उसका _id उपयोग करें। यह 204 No Content लौटाता है। यदि आप उसी टास्क को फिर से फ़ेच करने की कोशिश करते हैं, तो आपको 404 के साथ "task not found" मिलेगा।
DELETE http://localhost:3000/tasks/<paste-task-id-here>
इस चरण में, आपने पुष्टि कर ली है कि सभी रूट अपेक्षा के अनुसार काम करते हैं। API अपना काम कर रही है, इसलिए अब हम इसके ऊपर एक UI बनाना शुरू कर सकते हैं।
स्टेप 8: API से इंटरैक्ट करने के लिए फ्रंटएंड जोड़ें
Postman API की जाँच के लिए अच्छा है, लेकिन एक वास्तविक वेबसाइट पर इसे काम करते देखना बेहतर होगा, इसलिए आइए अपने एप्लिकेशन में थोड़ा फ्रंटएंड कोड जोड़ते हैं।
यहाँ बड़े HTML और CSS फाइलें पेस्ट करने के बजाय, मैंने कोड इस GitHub रिपॉज़िटरी में जोड़ दिया है। वहाँ जाएँ और index.html, styles.css, और favicon.svg की सामग्री को अपने प्रोजेक्ट रूट के public/ फ़ोल्डर में कॉपी करें। बदलाव सेव करें और फिर localhost:3000 पर जाएँ। फिर नीचे दिया गया कोड app.js फाइल में पेस्ट करें:
const form = document.getElementById('task-form');
const titleEl = document.getElementById('title');
const descEl = document.getElementById('description');
const catEl = document.getElementById('category');
const errEl = document.getElementById('form-error');
const listEl = document.getElementById('task-list');
const emptyEl = document.getElementById('empty');
const filterEl = document.getElementById('filter');
const countEl = document.getElementById('task-count');
async function api(method, path, body) {
const res = await fetch(path, {
method,
headers: body ? { 'Content-Type': 'application/json' } : {},
body: body ? JSON.stringify(body) : undefined
});
if (res.status === 204) return null;
const data = await res.json();
if (!res.ok) throw new Error(data.error || `Request failed (${res.status})`);
return data;
}
const fetchTasks = (category) =>
api('GET', '/tasks' + (category ? `?category=${encodeURIComponent(category)}` : ''));
const createTask = (body) => api('POST', '/tasks', body);
const updateTask = (id, body) => api('PUT', `/tasks/${id}`, body);
const deleteTask = (id) => api('DELETE', `/tasks/${id}`);
function updateCount(tasks) {
const active = tasks.filter((t) => !t.completed).length;
const total = tasks.length;
if (total === 0) {
countEl.textContent = '';
return;
}
const scope = filterEl.value ? ` in ${filterEl.value}` : '';
countEl.textContent = `${total} tasks${scope} · ${active} active`;
}
function renderTask(task) {
const li = document.createElement('li');
li.className = 'task' + (task.completed ? ' completed' : '');
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.checked = task.completed;
checkbox.addEventListener('change', () =>
updateTask(task._id, { completed: checkbox.checked }).then(refresh)
);
const title = document.createElement('div');
title.textContent = task.title;
const badge = document.createElement('span');
badge.textContent = task.category;
const del = document.createElement('button');
del.textContent = 'Delete';
del.onclick = () => deleteTask(task._id).then(refresh);
li.append(checkbox, title, badge, del);
return li;
}
function render(tasks) {
listEl.innerHTML = '';
emptyEl.classList.toggle('hidden', tasks.length > 0);
for (const task of tasks) {
listEl.appendChild(renderTask(task));
}
updateCount(tasks);
}
async function refresh() {
try {
const tasks = await fetchTasks(filterEl.value);
render(tasks);
} catch (e) {
errEl.textContent = e.message;
}
}
form.addEventListener('submit', async (e) => {
e.preventDefault();
errEl.textContent = '';
const title = titleEl.value.trim();
if (!title) {
errEl.textContent = 'Title is required.';
return;
}
try {
await createTask({
title,
description: descEl.value.trim(),
category: catEl.value
});
form.reset();
await refresh();
} catch (e) {
errEl.textContent = e.message;
}
});
filterEl.addEventListener('change', refresh);
refresh();
JavaScript कोड वहीं है जहाँ सब कुछ आपकी बनाई API से जुड़ता है। हर फ़ंक्शन आपके किसी न किसी रूट से मैप होता है, जिससे फ्रंटएंड को फॉलो करना आसान रहता है। सभी रिक्वेस्ट भी एक सिंगल api() हेल्पर से होकर जाती हैं, इसलिए आपको हर जगह एक जैसा एरर हैंडलिंग लॉजिक दोहराने की ज़रूरत नहीं पड़ती।
फ़िल्टरिंग एक क्वेरी स्ट्रिंग जैसे ?category=Work पास करके काम करती है, जो सीधे उस बैकएंड लॉजिक से जुड़ती है जिसे आपने पहले जोड़ा है। किसी टास्क को बनाते, अपडेट करते या डिलीट करते समय, ऐप फिर से नवीनतम सूची फ़ेच करता है ताकि UI सिंक में रहे। टास्क काउंटर एक छोटा-सा विवरण है, लेकिन इससे ऐप का अनुभव ज़्यादा जीवंत लगता है।
अब npm run dev से सर्वर रिस्टार्ट करें, फिर http://localhost:3000 खोलें और टास्क मैनेजर आज़माएँ। कुछ टास्क जोड़ें, उन्हें अपडेट करें, कैटेगरी बदलें, और कुछ डिलीट भी करें। इस समय, फ्रंटएंड और बैकएंड पूरी तरह जुड़े हुए हैं और एंड-टू-एंड काम कर रहे हैं।
सारांश
बधाई हो! आपने Node.js, Express और MongoDB का उपयोग करके स्क्रैच से एक पूर्ण टास्क मैनेजर API बना ली है। आपने वैलिडेशन संभाला, अपने रूट्स को छोटा रखा, और सब कुछ मिलाकर एक काम करता हुआ सिस्टम बनाया। इससे भी महत्वपूर्ण, आपने चीज़ों को एब्स्ट्रैक्शन के पीछे छिपाने के बजाय MongoDB के वास्तविक तरीके के क़रीब रहते हुए काम किया। यहाँ से, आप ऐप को प्रोडक्शन-रेडी बनाने के लिए विकसित करना शुरू कर सकते हैं।
मुख्य बातें
- मिडलवेयर का उपयोग और वैलिडेशन का केंद्रीकरण आपके कोड को साफ़ और अनुमानित बनाए रखता है।
- नेटिव MongoDB ड्राइवर अनावश्यक एब्स्ट्रैक्शन के बिना आपको अधिक नियंत्रण देता है।
- अच्छी तरह संरचित API को प्रोडक्शन-रेडी ऐप में बढ़ाना आसान होता है।
FAQs
क्या MongoDB API बनाने के लिए मुझे Mongoose की ज़रूरत है?
नहीं। यह ट्यूटोरियल नेटिव MongoDB ड्राइवर का उपयोग करता है, जो आपको अधिक नियंत्रण देता है और चीज़ों को हल्का रखता है। यदि आपको स्कीमा एब्स्ट्रैक्शन चाहिए, तो आप बाद में Mongoose जोड़ सकते हैं।
स्कीमा के बिना डेटा को कैसे वैलिडेट करें?
आप वैलिडेशन को हेल्पर फ़ंक्शंस (जैसे buildTaskDocument) में केंद्रीकृत कर सकते हैं, जिससे हर रूट डेटाबेस में लिखने से पहले एक ही नियम लागू करता है।
यदि मैं अमान्य ObjectId पास करूँ तो क्या होगा?
मिडलवेयर इसे शुरुआती चरण में ही 400 प्रतिक्रिया के साथ अस्वीकार कर देता है, जिससे MongoDB के भ्रामक एरर्स से बचाव होता है।