├── .babelrc ├── .dockerignore ├── .gitignore ├── .nvmrc ├── Dockerfile ├── README.md ├── docker-compose.yml ├── docs └── screenshot.png ├── package-lock.json ├── package.json └── src ├── app.js ├── config.js ├── controllers └── tasks.controllers.js ├── index.js ├── model └── Task.js ├── public ├── css │ └── main.css └── icons │ └── empty.svg ├── routes └── tasks.routes.js ├── utils └── mongoose.js └── views ├── 404.hbs ├── edit.hbs ├── error.hbs ├── index.hbs ├── layouts └── main.hbs └── partials ├── navbar.hbs └── tasks ├── taskForm.hbs └── taskTable.hbs /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"], 3 | "plugins": ["@babel/plugin-transform-runtime"] 4 | } 5 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | docs 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | 4 | ## environment variables 5 | 6 | .env -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v16.13.0 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16 2 | 3 | RUN mkdir -p /usr/src/app 4 | 5 | WORKDIR /usr/src/app 6 | 7 | COPY package*.json ./ 8 | 9 | RUN npm install 10 | 11 | COPY . . 12 | 13 | EXPOSE 3000 14 | 15 | CMD ["npm", "run", "dev"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CRUD with Nodejs, Express and Mongodb 2 | 3 | ![](docs/screenshot.png) 4 | 5 | This is a Multi page application using nodejs mongodb and handlebars 6 | 7 | ### Installation with docker-compose (Recommended) 8 | 9 | ```bash 10 | git clone https://github.com/FaztWeb/express-mongodb-crud 11 | cd express-mongodb-crud 12 | docker-compose up 13 | ``` 14 | 15 | ### Installation (Manually) 16 | 17 | #### Requirements 18 | 19 | * You need mongodb installed and running on your computer. or alternatively you can use docker 20 | 21 | ```bash 22 | git clone https://github.com/FaztWeb/express-mongodb-crud 23 | cd express-mongodb-crud 24 | npm install 25 | npm run build 26 | npm start 27 | ``` 28 | 29 | Now you can visit: http://localhost:3000 30 | 31 | ### Todo 32 | 33 | * [ ] add search input to find tasks 34 | * [ ] add usert authentication and authorization 35 | * [ ] add docker configuration for production 36 | * [ ] add github actions setup 37 | * [ ] add connect-mongo to store session in db 38 | * [ ] remove babel with es modules 39 | * [ ] add error handling 40 | * [ ] add a validation library for user input 41 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | services: 4 | web: 5 | container_name: tasksapp 6 | restart: always 7 | build: . 8 | ports: 9 | - 3000:3000 10 | links: 11 | - mongo 12 | volumes: 13 | - .:/usr/src/app 14 | mongo: 15 | container_name: tasksdb 16 | image: mongo 17 | ports: 18 | - "27017:27017" 19 | logging: 20 | driver: none 21 | -------------------------------------------------------------------------------- /docs/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FaztWeb/express-mongodb-crud/c5866af78f951b21c9262cddab8c96715bd6e2c3/docs/screenshot.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-mongodb-crud", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "dependencies": { 7 | "dotenv": "^10.0.0", 8 | "express": "^4.17.2", 9 | "express-handlebars": "^6.0.2", 10 | "mongoose": "^6.1.5", 11 | "morgan": "^1.10.0" 12 | }, 13 | "devDependencies": { 14 | "@babel/cli": "^7.16.7", 15 | "@babel/core": "^7.16.7", 16 | "@babel/node": "^7.16.7", 17 | "@babel/plugin-transform-runtime": "^7.16.7", 18 | "@babel/preset-env": "^7.16.7", 19 | "ncp": "^2.0.0", 20 | "nodemon": "^2.0.15" 21 | }, 22 | "scripts": { 23 | "dev": "nodemon ./src --exec babel-node", 24 | "build": "babel src -d dist && ncp src/views dist/views && ncp src/public dist/public", 25 | "start": "node ./dist" 26 | }, 27 | "keywords": [], 28 | "author": "", 29 | "license": "ISC" 30 | } 31 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import express from "express"; 3 | import morgan from "morgan"; 4 | import { create } from "express-handlebars"; 5 | 6 | import indexRoutes from "./routes/tasks.routes"; 7 | 8 | const app = express(); 9 | 10 | // settings 11 | app.set("port", process.env.PORT || 3000); 12 | app.set("views", path.join(__dirname, "views")); 13 | app.engine( 14 | ".hbs", 15 | create({ 16 | layoutsDir: path.join(app.get("views"), "layouts"), 17 | partialsDir: path.join(app.get("views"), "partials"), 18 | defaulLayout: "main", 19 | extname: ".hbs", 20 | }).engine 21 | ); 22 | app.set("view engine", ".hbs"); 23 | 24 | // middlewares 25 | app.use(morgan("dev")); 26 | app.use(express.urlencoded({ extended: false })); 27 | 28 | // routes 29 | app.use(indexRoutes); 30 | 31 | // public route 32 | app.use(express.static(path.join(__dirname, "public"))); 33 | 34 | app.use((req, res, next) => { 35 | res.status(404).render("404"); 36 | }); 37 | 38 | export default app; 39 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | import { config } from "dotenv"; 2 | 3 | config(); 4 | 5 | export const MONGODB_URI = 6 | process.env.MONGODB_URI || "mongodb://localhost/test"; 7 | -------------------------------------------------------------------------------- /src/controllers/tasks.controllers.js: -------------------------------------------------------------------------------- 1 | import Task from "../model/Task"; 2 | 3 | export const renderTasks = async (req, res) => { 4 | try { 5 | const tasks = await Task.find().lean(); 6 | res.render("index", { 7 | tasks, 8 | }); 9 | } catch (error) { 10 | console.log({ error }); 11 | return res.render("error", { errorMessage: error.message }); 12 | } 13 | }; 14 | 15 | export const createTask = async (req, res, next) => { 16 | try { 17 | const task = new Task(req.body); 18 | await task.save(); 19 | res.redirect("/"); 20 | } catch (error) { 21 | return res.render("error", { errorMessage: error.message }); 22 | } 23 | }; 24 | 25 | export const taskToggleDone = async (req, res, next) => { 26 | let { id } = req.params; 27 | const task = await Task.findById(id); 28 | task.done = !task.done; 29 | await task.save(); 30 | res.redirect("/"); 31 | }; 32 | 33 | export const renderTaskEdit = async (req, res, next) => { 34 | const task = await Task.findById(req.params.id).lean(); 35 | res.render("edit", { task }); 36 | }; 37 | 38 | export const editTask = async (req, res, next) => { 39 | const { id } = req.params; 40 | await Task.updateOne({ _id: id }, req.body); 41 | res.redirect("/"); 42 | }; 43 | 44 | export const deleteTask = async (req, res, next) => { 45 | let { id } = req.params; 46 | await Task.remove({ _id: id }); 47 | res.redirect("/"); 48 | }; 49 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import app from "./app"; 2 | import "./utils/mongoose"; 3 | 4 | app.listen(app.get("port")); 5 | console.log(`server on port ${app.get("port")}`); 6 | -------------------------------------------------------------------------------- /src/model/Task.js: -------------------------------------------------------------------------------- 1 | import { Schema, model } from "mongoose"; 2 | 3 | const TaskSchema = Schema( 4 | { 5 | title: { type: String, required: true, trim: true, unique: true }, 6 | description: { 7 | type: String, 8 | trim: true, 9 | }, 10 | done: { 11 | type: Boolean, 12 | default: false, 13 | }, 14 | }, 15 | { 16 | timestamps: true, 17 | versionKey: false, 18 | } 19 | ); 20 | 21 | export default model("Task", TaskSchema); 22 | -------------------------------------------------------------------------------- /src/public/css/main.css: -------------------------------------------------------------------------------- 1 | .bg-black { 2 | background-color: #111111 !important; 3 | } -------------------------------------------------------------------------------- /src/public/icons/empty.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/routes/tasks.routes.js: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import { 3 | createTask, 4 | deleteTask, 5 | renderTasks, 6 | taskToggleDone, 7 | renderTaskEdit, 8 | editTask, 9 | } from "../controllers/tasks.controllers"; 10 | 11 | const router = Router(); 12 | 13 | // Render all tasks 14 | router.get("/", renderTasks); 15 | 16 | router.post("/tasks/add", createTask); 17 | 18 | router.get("/tasks/:id/toggleDone", taskToggleDone); 19 | 20 | router.get("/tasks/:id/edit", renderTaskEdit); 21 | 22 | router.post("/tasks/:id/edit", editTask); 23 | 24 | router.get("/tasks/:id/delete", deleteTask); 25 | 26 | export default router; 27 | -------------------------------------------------------------------------------- /src/utils/mongoose.js: -------------------------------------------------------------------------------- 1 | import { connect } from "mongoose"; 2 | import { MONGODB_URI } from "../config"; 3 | 4 | // connection to db 5 | (async () => { 6 | try { 7 | const db = await connect(MONGODB_URI); 8 | console.log("Db connectect to", db.connection.name); 9 | } catch (error) { 10 | console.error(error); 11 | } 12 | })(); 13 | -------------------------------------------------------------------------------- /src/views/404.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Not Found

5 | volver 6 |
7 |
8 |
-------------------------------------------------------------------------------- /src/views/edit.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 |
7 |

Update Task

8 | 9 |
10 |
11 | 18 |
19 |
20 | 26 |
27 | 28 |
29 |
30 |
31 |
32 |
33 |
-------------------------------------------------------------------------------- /src/views/error.hbs: -------------------------------------------------------------------------------- 1 |
2 |

{{errorMessage}}

3 |
-------------------------------------------------------------------------------- /src/views/index.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | {{> tasks/taskForm }} 6 |
7 | 8 |
9 | {{> tasks/taskTable }} 10 |
11 |
12 |
-------------------------------------------------------------------------------- /src/views/layouts/main.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Nodejs Mongodb CRUD 8 | 9 | 10 | 11 | 12 | 13 | 14 | {{>navbar}} 15 | 16 | {{{body}}} 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/views/partials/navbar.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/partials/tasks/taskForm.hbs: -------------------------------------------------------------------------------- 1 |
2 |

Add A Task

3 |
4 | 5 | {{! Title Input }} 6 |
7 | 8 | 16 |
17 | 18 | {{! Description Input }} 19 |
20 | 21 | 27 |
28 | 29 | 30 |
31 |
-------------------------------------------------------------------------------- /src/views/partials/tasks/taskTable.hbs: -------------------------------------------------------------------------------- 1 | {{#if tasks}} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | {{#each tasks}} 13 | 14 | 15 | 16 | 17 | 35 | 36 | {{/each}} 37 | 38 |
TitleDescriptionOperations
{{@index}}{{title}}{{description}} 18 | {{#if done}} 19 | Done 23 | {{else}} 24 | 25 | Undone 26 | 27 | {{/if}} 28 | 29 | Delete 30 | 31 | 32 | Edit 33 | 34 |
39 | {{else}} 40 |
41 |
42 |

No tasks

43 | 44 |
45 | 46 |
47 | {{/if}} --------------------------------------------------------------------------------