diff --git a/Backend Development/rest-api-demo/.env b/Backend Development/rest-api-demo/.env new file mode 100644 index 0000000..2bf4bf8 --- /dev/null +++ b/Backend Development/rest-api-demo/.env @@ -0,0 +1,4 @@ +MONGODB_LOCAL_URI = mongodb://localhost:27017/boilerplate +MONGODB_URI = "your production mongo url" + +NODE_ENV = "not production" \ No newline at end of file diff --git a/Backend Development/rest-api-demo/README.MD b/Backend Development/rest-api-demo/README.MD new file mode 100644 index 0000000..e7932c0 --- /dev/null +++ b/Backend Development/rest-api-demo/README.MD @@ -0,0 +1,20 @@ +This project is only a sample to look and recreate your own rest-api with concepts from this project. +It is not the super simple version, but it's not the most complex either. +It has clean structure for better understanding. + +Starting point for this project is server.js which imports app.js containing all the logic + +follow the following steps to create your own services + +1 create model +2 create service +3 create router +4 import and use the router in app.js + +NOTE: This project can be massively improved. I have kept it simple for the ease of understanding for beginners. + +Full version of this boiler-plate: https://github.com/officialabdulrehman/node-express-restapi-boilerplate + +Auther: https://github.com/officialabdulrehman + +Happy Coding!!! diff --git a/Backend Development/rest-api-demo/app.js b/Backend Development/rest-api-demo/app.js new file mode 100644 index 0000000..002bad2 --- /dev/null +++ b/Backend Development/rest-api-demo/app.js @@ -0,0 +1,22 @@ +import express from "express"; // import express + +import { DBConnect } from "./config/database/connection"; // import database connection function + +import { MONGODB_URI, PORT } from "./utils/secrets"; // import secrets +import { defaultErrorHandler, cors } from "./utils/apiHelpers"; // import helper functions + +import { userRouter } from "./routers/user/user"; // import router + +export const app = express(); // create app/server, store it in app - export +export default app; // default export + +DBConnect(MONGODB_URI); // connect database ( call DBConnect and pass mongo uri) + +app.use(cors); // enable cors + +app.set("port", PORT || 3000); // set server port +app.use(express.json()); // parse json + +app.use("/api/v1/user", userRouter); // initialize router here + +app.use(defaultErrorHandler); // handle errors at one place diff --git a/Backend Development/rest-api-demo/config/database/connection.js b/Backend Development/rest-api-demo/config/database/connection.js new file mode 100644 index 0000000..54f65cc --- /dev/null +++ b/Backend Development/rest-api-demo/config/database/connection.js @@ -0,0 +1,26 @@ +import mongoose from "mongoose"; +import bluebird from "bluebird"; + +export const DBConnect = async (mongoUrl) => { + mongoose.Promise = bluebird; + + const connectionP = new Promise((res, rej) => { + mongoose + .connect(mongoUrl) + .then((ins) => { + console.log("Mongo connected!!!"); + console.log( + `Using mongo host '${mongoose.connection.host}' and port '${mongoose.connection.port}'` + ); + return res(ins); + }) + .catch((err) => { + console.log( + `MongoDB connection error. Please make sure MongoDB is running. ${err}` + ); + return rej(err); + }); + }); + + return connectionP; +}; diff --git a/Backend Development/rest-api-demo/models/user.js b/Backend Development/rest-api-demo/models/user.js new file mode 100644 index 0000000..e5620e1 --- /dev/null +++ b/Backend Development/rest-api-demo/models/user.js @@ -0,0 +1,13 @@ +import mongoose from "mongoose"; + +const { Schema, model } = mongoose; + +const schemaFields = { + email: { type: String, required: true, unique: true }, + name: { type: String, required: false }, + age: { type: Number, required: false }, +}; + +const schema = new Schema(schemaFields, { timestamps: true }); + +export const UserModel = model("User", schema); diff --git a/Backend Development/rest-api-demo/package.json b/Backend Development/rest-api-demo/package.json new file mode 100644 index 0000000..c40e672 --- /dev/null +++ b/Backend Development/rest-api-demo/package.json @@ -0,0 +1,25 @@ +{ + "name": "server", + "version": "1.0.0", + "description": "rest-api-boilerplate-basic", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node index.js", + "nodemon": "nodemon --experimental-modules --es-module-specifier-resolution=node index.js" + }, + "author": "NizTheDev", + "license": "MIT", + "devDependencies": { + "nodemon": "^2.0.6" + }, + "dependencies": { + "axios": "^0.21.1", + "dotenv": "^8.2.0", + "express": "^4.17.1", + "express-validator": "^6.7.0", + "mongoose": "^5.13.7", + "validator": "^13.5.2" + }, + "type": "module" +} diff --git a/Backend Development/rest-api-demo/routers/user.js b/Backend Development/rest-api-demo/routers/user.js new file mode 100644 index 0000000..e4e00e9 --- /dev/null +++ b/Backend Development/rest-api-demo/routers/user.js @@ -0,0 +1,14 @@ +import { Router } from "express"; +import userService from "../services/user"; +import { response, restApiValidation } from "../utils/apiHelpers"; + +export const userRouter = Router(); +export default userRouter; + +userRouter.get("/", [], async (req, res, next) => { + restApiValidation(req, res, next); + const page = parseInt(req.params.page) || 1; + const perPage = parseInt(req.params.perPage) || 10; + const result = await userService.list(page, perPage); + response(res, result); +}); diff --git a/Backend Development/rest-api-demo/server.js b/Backend Development/rest-api-demo/server.js new file mode 100644 index 0000000..5604c8d --- /dev/null +++ b/Backend Development/rest-api-demo/server.js @@ -0,0 +1,11 @@ +import { app } from "./app"; // import app + +// run app by starting to listen +const server = app.listen(app.get("port"), () => { + console.log( + `App is running at http://localhost:${app.get("port")} in ${app.get( + "env" + )} mode` + ); + console.log(" Press CTRL-C to stop\n"); +}); diff --git a/Backend Development/rest-api-demo/services/user.js b/Backend Development/rest-api-demo/services/user.js new file mode 100644 index 0000000..3fc0d31 --- /dev/null +++ b/Backend Development/rest-api-demo/services/user.js @@ -0,0 +1,22 @@ +import { UserModel } from "../models/user"; + +class UserService { + transformUser(user) { + return { + email: user.email, + name: user.name, + age: user.age, + }; + } + + async list(page, perPage) { + let skip = (page - 1) * perPage; + skip = page > 1 ? skip - 1 : skip; + const limit = page > 1 ? perPage + 2 : perPage + 1; + const user = await UserModel.find({}).skip(skip).limit(limit).sort(sort); + return this.transformUser(user); + } +} + +export const userService = new UserService(userDAO); +export default userService; diff --git a/Backend Development/rest-api-demo/utils/apiHelpers.js b/Backend Development/rest-api-demo/utils/apiHelpers.js new file mode 100644 index 0000000..fe29625 --- /dev/null +++ b/Backend Development/rest-api-demo/utils/apiHelpers.js @@ -0,0 +1,47 @@ +export const defaultErrorHandler = (err, req, res, next) => { + const { code, message, data } = err; + res.status(code || 500).json({ + message: message || "Internal server error", + result: {}, + errors: data || [], + }); +}; + +export const cors = (req, res, next) => { + res.setHeader("Access-Control-Allow-Origin", "*"); + res.setHeader( + "Access-Control-Allow-Methods", + "GET, POST, PUT, PATCH, DELETE" + ); + res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); + if (req.method === "OPTIONS") return res.sendStatus(200); + next(); +}; +export default cors; + +export const restApiValidation = (req, res, next) => { + const errors = validationResult(req); + if (!errors.isEmpty()) { + const error = new Error(`Bad Request`); + error.data = errors.array().map((err) => { + return { + message: err.msg || "", + param: err.param || "", + location: err.location || "", + value: err.value || "", + }; + }); + error.code = 400; + throw error; + } + return true; +}; + +export const response = (res, result) => { + const response = { + message: "Success", + result: result, + errors: [], + }; + res.status(200).json(response); +}; diff --git a/Backend Development/rest-api-demo/utils/secrets.js b/Backend Development/rest-api-demo/utils/secrets.js new file mode 100644 index 0000000..e49b42d --- /dev/null +++ b/Backend Development/rest-api-demo/utils/secrets.js @@ -0,0 +1,22 @@ +import dotenv from "dotenv"; +dotenv.config(); + +const ENVIRONMENT = process.env.NODE_ENV; +const prod = ENVIRONMENT === "production"; +export const MONGODB_URI = prod + ? process.env["MONGODB_URI"] + : process.env["MONGODB_LOCAL_URI"]; + +export const PORT = process.env.PORT; + +if (!MONGODB_URI) { + if (prod) { + throw new Error( + "No mongo connection string. Set MONGODB_URI environment variable." + ); + } else { + throw new Error( + "No mongo connection string. Set MONGODB_URI_LOCAL environment variable." + ); + } +}