{"id":22356478,"url":"https://github.com/dylancheetah/mongo-todo","last_synced_at":"2026-04-13T23:32:01.817Z","repository":{"id":239123390,"uuid":"798491541","full_name":"DylanCheetah/mongo-todo","owner":"DylanCheetah","description":"A todo list REST API that demonstrates basic backend development concepts.","archived":false,"fork":false,"pushed_at":"2024-06-05T18:11:21.000Z","size":52,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-26T13:15:19.396Z","etag":null,"topics":["backend","express","mongodb","nodejs","rest-api","todo-list","tutorial"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/DylanCheetah.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-05-09T22:16:54.000Z","updated_at":"2024-06-05T18:11:24.000Z","dependencies_parsed_at":"2024-05-10T06:42:35.120Z","dependency_job_id":"b49a8dc0-3b88-4857-9656-d54e6b804a31","html_url":"https://github.com/DylanCheetah/mongo-todo","commit_stats":null,"previous_names":["dylancheetah/mongo-todo"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/DylanCheetah/mongo-todo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DylanCheetah%2Fmongo-todo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DylanCheetah%2Fmongo-todo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DylanCheetah%2Fmongo-todo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DylanCheetah%2Fmongo-todo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DylanCheetah","download_url":"https://codeload.github.com/DylanCheetah/mongo-todo/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DylanCheetah%2Fmongo-todo/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31775744,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-13T20:17:16.280Z","status":"ssl_error","status_checked_at":"2026-04-13T20:17:08.216Z","response_time":93,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["backend","express","mongodb","nodejs","rest-api","todo-list","tutorial"],"created_at":"2024-12-04T14:10:49.042Z","updated_at":"2026-04-13T23:32:01.762Z","avatar_url":"https://github.com/DylanCheetah.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# mongo-todo\nA todo list REST API that demonstrates basic backend development concepts. This project is a single-tenant application intended for educational purposes and is not suitable for production.\n\n\n## Required Software\n* Node.js\n* Visual Studio Code\n* curl\n\n\n## Tutorial\n### Phase 1: Setup MongoDB\nFor this project, we will be developing a todo list backend that uses MongoDB for storage. Before we can start, we will need to setup MongoDB. There are multiple ways we can do this. We can install MongoDB locally or we can use MongoDB Atlas. I will give instructions for both methods.\n\n#### Local Installation on Windows\n1. visit https://www.mongodb.com/try/download/community\n2. from the Package dropdown, choose \"zip\"\n3. click the Download button\n4. unzip the zip file in a convenient location\n5. navigate to the `bin` folder within the extracted folder\n6. inside you should see an executable called `mongod`\n7. create a folder called `data` inside the `bin` folder\n8. now open a terminal to the `bin` folder\n9. execute `mongod --dbpath ./data` to start the database server and initialize the database\n\n#### Local Installation on Linux\nTODO\n\n#### MongoDB Atlas\n1. visit https://www.mongodb.com/cloud/atlas/register\n2. create a MongoDB Atlas account\n3. follow the provided instructions to setup a MongoDB Atlas cluster\n\n### Phase 2: Setup Backend\nNext we need to setup a new project for our backend. We will be using nodemon, express, and mongodb for our backend.\n\n01. create a folder called `mongo-todo`\n02. open the folder in VS Code\n03. click Terminal \u003e New Terminal\n04. execute `npm init`\n05. answer the prompts\n06. execute `npm install --save-dev nodemon` to install nodemon\n07. execute `npm install --save express mongodb` to install express and mongodb\n08. create `index.js` with the following content:\n```js\n/*\n * Mongo Todo\n *\n * This is a todo list REST API that uses MongoDB for storage.\n */\n\n// Import express and create an app object\nconst express = require(\"express\");\nconst app = express();\nconst HOST = process.env.HOST || \"127.0.0.1\";\nconst PORT = parseInt(process.env.PORT || \"8000\");\n\n// Define routes\napp.get(\"/\", (req, res) =\u003e {\n    res.send(`\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n    \u003chead\u003e\n        \u003ctitle\u003eMongo Todo\u003c/title\u003e\n    \u003c/head\u003e\n    \u003cbody\u003e\n        \u003ch1\u003eMongo Todo\u003c/h1\u003e\n    \u003c/body\u003e\n\u003c/html\u003e`);\n});\n\n// Listen for incoming connections\napp.listen(PORT, HOST, () =\u003e {\n    console.log(`Listening at http://${HOST}:${PORT}/...`);\n});\n```\n09. open `package.json` and add debug and start scripts like this:\n```json\n{\n  ...\n  \"scripts\": {\n    \"debug\": \"nodemon index.js\",\n    \"start\": \"node index.js\",\n    ...\n  }\n  ...\n}\n```\n10. execute `npm run debug` to start the backend\n11. execute `curl http://127.0.0.1:8000` and you should see the following output:\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n    \u003chead\u003e\n        \u003ctitle\u003eMongo Todo\u003c/title\u003e\n    \u003c/head\u003e\n    \u003cbody\u003e\n        \u003ch1\u003eMongo Todo\u003c/h1\u003e\n    \u003c/body\u003e\n\u003c/html\u003e\n```\n\n### Phase 3: Connect Backend to MongoDB\nNow that we have the basic framework for our backend completed, we need to connect it to our database. To do this, we will need to create a module that provides an API for connecting to our database server.\n\n1. create `db.js` with the following content:\n```js\n/*\n * Mongo Todo - Database API\n *\n * This module provides access to the database used by this web app.\n */\n\n// Import mongodb and create a database client\nconst MongoClient = require(\"mongodb\").MongoClient;\nconst client = new MongoClient(process.env.DB_URL || \"mongodb://127.0.0.1/mongo-todo\");\nlet db = null;\n\nmodule.exports = function() {\n    return new Promise((resolve, reject) =\u003e {\n        // If a connection has already been established, return the cached database object\n        if(db) {\n            return resolve(db);\n        }\n\n        // Connect to the database\n        client.connect()\n        .then(() =\u003e {\n            // Cache the database object\n            db = client.db();\n\n            // Return the database object\n            resolve(db);\n        })\n        .catch((err) =\u003e {\n            reject(err);\n        });\n    });\n};\n```\n\n### Phase 4: Setup REST API Routes\nNext we need to setup our REST API routes. These routes will be used to manipulate data in the database via HTTP requests.\n\n1. create a `routes` folder inside the project folder\n2. create `routes/task.js` with the following content:\n```js\n/*\n * Mongo Todo - Task API\n *\n * This is the REST API for tasks in the todo list.\n */\n\n// Import express and create a router\nconst express = require(\"express\");\nconst router = express.Router();\nmodule.exports = router;\n\n// Define routes\nrouter.post(\"/task\", (req, res) =\u003e {\n    // TODO: Implement Route Logic\n    console.log(`Payload: ${req.body}`);\n    res.send(\"Not Implemented\");\n});\n\nrouter.get(\"/tasks\", (req, res) =\u003e {\n    // TODO: Implement Route Logic\n    res.send(\"Not Implemented\");\n});\n\nrouter.put(\"/task/:id\", (req, res) =\u003e {\n    // TODO: Implement Route Logic\n    console.log(`ID: ${req.params.id}`);\n    console.log(`Payload: ${req.body}`);\n    res.send(\"Not Implemented\");\n});\n\nrouter.delete(\"/task/:id\", (req, res) =\u003e {\n    // TODO: Implement Route Logic\n    console.log(`ID: ${req.params.id}`);\n    res.send(\"Not Implemented\");\n});\n```\n3. open `index.js` and add the following to the end of your routes section:\n```js\napp.use(\"/api/v1/\", require(\"./routes/task\"));\n```\n4. you also need to add this above your routes section:\n```js\n// Configure middleware\napp.use(express.json());\n```\n5. now you should be able to ping each route with curl like this:\n```shell\ncurl -X POST --json \"{\\\"name\\\": \\\"Wash Laundry\\\"}\" http://127.0.0.1:8000/api/v1/task\ncurl http://127.0.0.1:8000/api/v1/tasks\ncurl -X PUT --json \"{\\\"completed\\\": true}\" http://127.0.0.1:8000/api/v1/task/1\ncurl -X DELETE http://127.0.0.1:8000/api/v1/task/1\n```\n6. right now each route should respond with \"Not Implemented\" and you should see some additional output in the server logs\n\n### Phase 5: Unit Testing\nAs you may have noticed, testing each endpoint with curl becomes tedious and time-consuming. It's also easier to make a mistake while testing. Fortunately for us, there is a better way to test our REST API endpoints. If we install Mocha and node-fetch, we can do automated unit testing of our REST API endpoints. Mocha is a unit testing framework for Node.js and node-fetch provides a convenient way to send HTTP requests.\n\n1. execute `npm install --save-dev mocha` to install Mocha\n2. execute `npm install --save node-fetch@2.7.0` to install node-fetch\n3. create a `test` folder inside your project folder\n4. create `test/task.js` with the following content:\n```js\n/*\n * Mongo Todo - Unit Tests for Task API\n *\n * These are the unit tests for the task API.\n */\n\n// Import node-fetch and db\nconst fetch = require(\"node-fetch\");\nlet db = null;\n\nrequire(\"../db\")()\n.then((_db) =\u003e {\n    db = _db;\n})\n.catch((err) =\u003e {\n    console.error(err);\n});\n\n// Run tests\ndescribe(\"Task\", function() {\n    let taskID = null;\n\n    describe(\"POST\", function() {\n        it(\"valid request\", function(done) {\n            // Post a new task\n            fetch(\"http://127.0.0.1:8000/api/v1/task\", {\n                method: \"POST\",\n                headers: {\n                    \"Content-Type\": \"application/json\"\n                },\n                body: JSON.stringify({\n                    name: \"Wash Laundry\"\n                })\n            })\n            .then((response) =\u003e {\n                // Check response code\n                if(response.status == 201) {\n                    done();\n                } else {\n                    done({msg: `Response code was ${response.status}. Expected 201 instead.`});\n                }\n            })\n            .catch((err) =\u003e {\n                done(err);\n            });\n        });\n\n        it(\"invalid request\", function(done) {\n            // Try to post a new task with invalid input\n            fetch(\"http://127.0.0.1:8000/api/v1/task\", {\n                method: \"POST\",\n                headers: {\n                    \"Content-Type\": \"application/json\"\n                },\n                body: JSON.stringify({\n                    trash: \"invalid trash\"\n                })\n            })\n            .then((response) =\u003e {\n                // Check response code\n                if(response.status == 400) {\n                    done();\n                } else {\n                    done({msg: `Response code was ${response.status}. Expected 400 instead.`});\n                }\n            })\n            .catch((err) =\u003e {\n                done(err);\n            });\n        });\n    });\n\n    describe(\"GET\", function() {\n        it(\"valid request\", function(done) {\n            // Get all tasks\n            fetch(\"http://127.0.0.1:8000/api/v1/tasks\")\n            .then((response) =\u003e response.json())\n            .then((payload) =\u003e {\n                // Is the payload valid?\n                if(payload.tasks.length \u0026\u0026 payload.tasks[0].name == \"Wash Laundry\") {\n                    taskID = payload.tasks[0]._id;\n                    done();\n                } else {\n                    done({msg: \"The received payload did not match the expected payload.\"});\n                }\n            })\n            .catch((err) =\u003e {\n                done(err);\n            })\n        });\n    });\n\n    describe(\"PUT\", function() {\n        it(\"valid request\", function(done) {\n            // Update a task\n            fetch(`http://127.0.0.1:8000/api/v1/task/${taskID}`, {\n                method: \"PUT\",\n                headers: {\n                    \"Content-Type\": \"application/json\"\n                },\n                body: JSON.stringify({\n                    completed: true\n                })\n            })\n            .then((response) =\u003e {\n                // Check response code\n                if(response.status == 204) {\n                    done();\n                } else {\n                    done({msg: `Response code was ${response.status}. Expected 204 instead.`});\n                }\n            })\n            .catch((err) =\u003e {\n                done(err);\n            });\n        });\n\n        it(\"invalid request\", function(done) {\n            // Try invalid task update request\n            fetch(`http://127.0.0.1:8000/api/v1/task/${taskID}`, {\n                method: \"PUT\",\n                body: JSON.stringify({\n                    trash: \"invalid trash\"\n                })\n            })\n            .then((response) =\u003e {\n                // Check response code\n                if(response.status == 400) {\n                    done();\n                } else {\n                    done({msg: `Response code was ${response.status}. Expected 400 instead.`});\n                }\n            })\n            .catch((err) =\u003e {\n                done(err);\n            });\n        });\n\n        it(\"verify data integrity\", function(done) {\n            // Fetch all tasks\n            fetch(\"http://127.0.0.1:8000/api/v1/tasks\")\n            .then((response) =\u003e response.json())\n            .then((payload) =\u003e {\n                // Check if task \"Wash Laundry\" is now completed\n                if(payload.tasks[0].completed) {\n                    done();\n                } else {\n                    done({msg: \"Task 'Wash Laundry' was not completed.\"})\n                }\n            })\n            .catch((err) =\u003e {\n                done(err);\n            })\n        });\n    });\n\n    describe(\"DELETE\", function() {\n        it(\"valid request\", function(done) {\n            // Delete a task\n            fetch(`http://127.0.0.1:8000/api/v1/task/${taskID}`, {\n                method: \"DELETE\"\n            })\n            .then((response) =\u003e {\n                // Check response code\n                if(response.status == 204) {\n                    done();\n                } else {\n                    done({msg: `Response code was ${response.status}. Expected 204 instead.`});\n                }\n            })\n            .catch((err) =\u003e {\n                done(err);\n            })\n        });\n\n        it(\"invalid request\", function(done) {\n            // Try to send invalid delete request\n            fetch(\"http://127.0.0.1:8000/api/v1/task/250\", {\n                method: \"DELETE\"\n            })\n            .then((response) =\u003e {\n                // Check response code\n                if(response.status == 500) {\n                    done();\n                } else {\n                    done({msg: `Response code was ${response.status}. Expected 500 instead.`});\n                }\n            })\n        });\n\n        it(\"verify data integrity\", function(done) {\n            // Fetch all tasks\n            fetch(\"http://127.0.0.1:8000/api/v1/tasks\")\n            .then((response) =\u003e response.json())\n            .then((payload) =\u003e {\n                // Check if the tasks list is empty\n                if(!payload.tasks.length) {\n                    done();\n                } else {\n                    done({msg: \"The task list should be empty but it isn't.\"});\n                }\n            })\n            .catch((err) =\u003e {\n                done(err);\n            });\n        });\n    });\n\n    // Cleanup\n    this.afterAll(function() {\n        // Drop tasks collection\n        db.dropCollection(\"tasks\")\n        .then(() =\u003e {\n            db.client.close();\n        });\n    });\n});\n```\n5. open `package.json` and change the test script like this:\n```json\n{\n  ...\n  \"scripts\": {\n    ...\n    \"test\": \"mocha\"\n  }\n  ...\n}\n```\n6. execute `npm test` to run the tests\n7. at this point, all tests should fail since none of the REST API endpoints have been implemented\n\n### Phase 6: Implement REST API Endpoints\nNow that we have automated unit testing setup, we can implement our REST API endpoints and test them automatically.\n\n1. open `routes/task.js`\n2. add this code above the route definitions:\n```js\n// Import database API\nlet db = null;\n\nrequire(\"../db\")()\n.then((_db) =\u003e {\n    db = _db;\n})\n.catch((err) =\u003e {\n    console.error(err);\n});\n\nconst ObjectId = require(\"mongodb\").ObjectId;\n```\n3. replace the body of the route for the `POST /api/v1/task` endpoint with the following:\n```js\n// Validate task data\nif(!req.body.name) {\n    return res.status(400).end();\n}\n\n// Add the task to the database\ndb.collection(\"tasks\").insertOne(req.body)\n.then(() =\u003e {\n    return res.status(201).end();\n})\n.catch((err) =\u003e {\n    return res.status(500).json({msg: \"A database error has occurred.\"});\n});\n```\n4. replace the body of the route for the `GET /api/v1/tasks` endpoint with the following:\n```js\n// Fetch all tasks from the database\ndb.collection(\"tasks\").find().toArray()\n.then((tasks) =\u003e {\n    return res.json({\n        tasks: tasks\n    });\n})\n.catch((err) =\u003e {\n    return res.status(500).json({msg: \"A database error has occurred.\"});\n});\n```\n5. replace the body of the route for the `PUT /api/v1/task/:id` endpoint with the following:\n```js\n// Validate update data\nif(!req.body.name \u0026\u0026 !req.body.completed) {\n    return res.status(400).json({msg: \"Field 'name' or 'completed' is required.\"});\n}\n\n// Update the given task\nconst updateData = {};\n\nif(req.body.name) {\n    updateData.name = req.body.name;\n}\n\nif(req.body.completed) {\n    updateData.completed = req.body.completed;\n}\n\ndb.collection(\"tasks\").updateOne({_id: ObjectId.createFromHexString(req.params.id)}, {$set: updateData})\n.then(() =\u003e {\n    return res.status(204).end();\n})\n.catch((err) =\u003e {\n    return res.status(500).json({msg: \"A database error occurred.\"});\n});\n```\n6. replace the body of the route for the `DELETE /api/v1/task/:id` endpoint with:\n```js\n// Delete the task\ndb.collection(\"tasks\").deleteOne({_id: ObjectId.createFromHexString(req.params.id)})\n.then(() =\u003e {\n    return res.status(204).end();\n})\n.catch((err) =\u003e {\n    return res.status(500).json({msg: \"A database error occurred.\"});\n});\n```\n7. execute `npm test` and all tests should pass now\n\n### Phase 7: Optimization\nNow that everything is working correctly, let's optimize our database. As it is right now, whenever we do a PUT or DELETE operation, MongoDB has to do a full scan of the collection in order to find the task we need to update or delete. This is very inefficient. Especially for large data sets. However, we can improve this by creating an index on the `_id` field of each document in our `tasks` collection.\n\n1. open `db.js`\n2. add the following code above the line where you resolve the promise:\n```js\n// Create indexes\nclient.db().collection(\"tasks\").createIndex({_id: 1});\n```\n3. execute `npm test` and you should notice an improvement in speed\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdylancheetah%2Fmongo-todo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdylancheetah%2Fmongo-todo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdylancheetah%2Fmongo-todo/lists"}