{"id":30328760,"url":"https://github.com/hidayet-aydin/api-based-blog-project","last_synced_at":"2026-04-10T02:58:00.937Z","repository":{"id":277623866,"uuid":"422729799","full_name":"hidayet-aydin/api-based-blog-project","owner":"hidayet-aydin","description":"This project is backend REST API of a multi-user supported blog site.","archived":false,"fork":false,"pushed_at":"2021-11-06T21:46:59.000Z","size":42,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-15T01:27:16.704Z","etag":null,"topics":["bcryptjs","blog","blog-api","chai","express","expressjs","jwt","jwt-authentication","mocha","mocha-chai","mongoose","morgan","multer","multiuser","node","nodejs","rest-api","restful","restful-api","winston"],"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/hidayet-aydin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","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":"2021-10-29T22:39:04.000Z","updated_at":"2021-11-10T20:17:37.000Z","dependencies_parsed_at":"2025-04-10T14:30:41.114Z","dependency_job_id":null,"html_url":"https://github.com/hidayet-aydin/api-based-blog-project","commit_stats":null,"previous_names":["hidayet-aydin/api-based-blog-project"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/hidayet-aydin/api-based-blog-project","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hidayet-aydin%2Fapi-based-blog-project","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hidayet-aydin%2Fapi-based-blog-project/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hidayet-aydin%2Fapi-based-blog-project/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hidayet-aydin%2Fapi-based-blog-project/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hidayet-aydin","download_url":"https://codeload.github.com/hidayet-aydin/api-based-blog-project/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hidayet-aydin%2Fapi-based-blog-project/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270932604,"owners_count":24670244,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-17T02:00:09.016Z","response_time":129,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["bcryptjs","blog","blog-api","chai","express","expressjs","jwt","jwt-authentication","mocha","mocha-chai","mongoose","morgan","multer","multiuser","node","nodejs","rest-api","restful","restful-api","winston"],"created_at":"2025-08-18T01:42:16.881Z","updated_at":"2026-04-10T02:57:55.901Z","avatar_url":"https://github.com/hidayet-aydin.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003eA REST API Based Blog\u003c/h1\u003e\n\u003cp align=\"center\"\u003eThis application works as a REST API that Users can manage to list, add, delete, edit their own blogs.\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n    \u003ca href=\"https://github.com/hidayet-aydin/api-based-blog-project/blob/main/LICENSE.txt\"\u003e\u003cimg src=\"https://img.shields.io/github/license/arifszn/article-api\"/\u003e\u003c/a\u003e\n\u003c/p\u003e\n\u003cbr/\u003e\n\n![Node.js](https://img.shields.io/badge/node.js-%232c3e50?style=for-the-badge\u0026logo=nodedotjs)\n![Express](https://img.shields.io/badge/express-%232c3e50?style=for-the-badge\u0026logo=express)\n![Nodemon](https://img.shields.io/badge/nodemon-%232c3e50?style=for-the-badge\u0026logo=nodemon)\n![Mocha](https://img.shields.io/badge/mocha-%232c3e50?style=for-the-badge\u0026logo=mocha)\n![Chai](https://img.shields.io/badge/chai-%232c3e50?style=for-the-badge\u0026logo=chai)\n![JS](https://img.shields.io/badge/javascript-%232c3e50?style=for-the-badge\u0026logo=javascript)\n![MongoDB](https://img.shields.io/badge/mongodb-%232c3e50?style=for-the-badge\u0026logo=mongodb)\n![JWT](https://img.shields.io/badge/JWT-%232c3e50?style=for-the-badge\u0026logo=jsonwebtokens)\n![GIT](https://img.shields.io/badge/git-%232c3e50?style=for-the-badge\u0026logo=git)\n\n**Development Modules**\n\n```bash\n$ npm i --save-dev nodemon nyc supertest mocha chai sinon sinon-chai chai-as-promised rewire\n```\n\n**Production Modules**\n\n```bash\n$ npm i --save express mongoose bcryptjs jsonwebtoken dotenv express-validator winston morgan multer aws-s3\n```\n\n**Initial Files**\n\n```bash\n$ touch .env .gitignore index.js README.md\n```\n\n**API Folder Structure**\n\n```bash\n$ mkdir server storage\n$ mkdir server/routes server/models server/controllers server/utils server/middlewares\n```\n\n## 1. MongoDB Create Special User and Collection\n\n```bash\n\u003e use nodejs-db\n```\n\n```js\ndb.createUser({\n  user: \"test-user\",\n  pwd: \"newpassword\",\n  roles: [{ role: \"readWrite\", db: \"api-based-blog\" }],\n});\n```\n\nFor a example, MongoDB connection string below is as follows. And, this connection string should be added to \".env\" file.\n\n```\nMONGODB_URI='mongodb://test-user:newpassword@localhost:27017/api-based-blog'\n```\n\n## 2. Logger (with Winston)\n\n```bash\n$ touch server/utils/logger.js\n```\n\n```js\nconst { config, createLogger, format, transports } = require(\"winston\");\nconst { combine, printf, timestamp, prettyPrint } = format;\n\nexports.httpLogger = createLogger({\n  level: \"info\",\n  format: combine(\n    printf(({ level, message }) =\u003e {\n      let msg_parts = message.split(\" | | \");\n      return `{\"${level}\": {\"combined\": \"${msg_parts[0]}\", \"headers\": ${msg_parts[1]}}},`;\n    })\n  ),\n  transports: [\n    new transports.Console(),\n    new transports.File({ filename: \"./logs/http.json\" }),\n  ],\n});\n```\n\n**Output (./logs/http.json):**\n\n```json\n{\n  \"info\": {\n    \"combined\": \"2021-11-02T002849.370Z | ::1 | HTTP/1.1 | POST | 201 | /auth/login | 368.041(ms)\",\n    \"headers\": {\n      \"content-type\": \"application/json\",\n      \"user-agent\": \"PostmanRuntime/7.28.4\",\n      \"accept\": \"*/*\",\n      \"postman-token\": \"6021275c-b747-448d-962a-af17724bdcc3\",\n      \"host\": \"localhost:3000\",\n      \"accept-encoding\": \"gzip, deflate, br\",\n      \"connection\": \"keep-alive\",\n      \"content-length\": \"59\"\n    }\n  }\n}\n```\n\n## 3. Cross-Origin Resource Sharing (CORS) Error handling Adding\n\n```bash\n$ touch server/middlewares/cors.js\n```\n\n```js\nmodule.exports = (req, res, next) =\u003e {\n  res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n  res.setHeader(\n    \"Access-Control-Allow-Methods\",\n    \"GET, POST, PUT, PATCH, DELETE\"\n  );\n  res.setHeader(\"Access-Control-Allow-Headers\", \"Content-Type, Authorization\");\n  next();\n};\n```\n\n## 4. HTTP Logger Middleware (with Morgan)\n\n```bash\n$ touch server/middlewares/http-logger.js\n```\n\n```js\nconst morgan = require(\"morgan\");\n\nmorgan.token(\"timer\", function (req, res) {\n  return new Date().toISOString();\n});\n\nmorgan.token(\"headers\", function (req, res) {\n  return JSON.stringify(req.headers);\n});\n\nconst responseFormat = [\n  \":timer\", //\":date[clf]\",\n  \":remote-addr\",\n  \"HTTP/:http-version\",\n  \":method\",\n  \":status\",\n  \":url\",\n  \":response-time(ms)\",\n  \"| :headers\",\n].join(\" | \");\n\nexports.successHttp = (logger) =\u003e\n  morgan(responseFormat, {\n    skip: (req, res) =\u003e res.statusCode \u003e= 400,\n    stream: { write: (message) =\u003e logger.log(\"info\", message) },\n  });\n\nexports.errorHttp = (logger) =\u003e\n  morgan(responseFormat, {\n    skip: (req, res) =\u003e res.statusCode \u003c 400,\n    stream: { write: (message) =\u003e logger.log(\"error\", message) },\n  });\n```\n\n## 5. Image Upload Middleware (with Multer)\n\n```\n$ touch server/middlewares/image-storage.js\n```\n\n```js\nconst multer = require(\"multer\");\n\nconst fileStorage = multer.diskStorage({\n  destination: (req, file, cb) =\u003e {\n    cb(null, \"storage\");\n  },\n  filename: (req, file, cb) =\u003e {\n    cb(\n      null,\n      (new Date().toISOString() + \"-\" + file.originalname).replace(/:/g, \"\")\n    );\n  },\n});\n\nconst fileFilter = (req, file, cb) =\u003e {\n  if (\n    file.mimetype === \"image/png\" ||\n    file.mimetype === \"image/jpg\" ||\n    file.mimetype === \"image/jpeg\"\n  ) {\n    cb(null, true);\n  } else {\n    cb(null, false);\n  }\n};\n\nmodule.exports = multer({\n  storage: fileStorage,\n  fileFilter: fileFilter,\n}).single(\"image\");\n```\n\n## 6. Authentication Check Middleware (with JSON WEB TOKEN)\n\n```\n$ touch server/middlewares/is-auth.js\n```\n\n```js\nconst { verify } = require(\"jsonwebtoken\");\n\nmodule.exports = (req, res, next) =\u003e {\n  const token = req.get(\"Authorization\").split(\" \")[1];\n  let decodedToken;\n  try {\n    decodedToken = verify(token, process.env.JWT_SECRET);\n  } catch (err) {\n    err.statusCode = 500;\n    throw err;\n  }\n  if (!decodedToken) {\n    const error = new Error(\"Not authenticated.\");\n    error.statusCode = 401;\n    throw error;\n  }\n  req.userId = decodedToken.userId;\n  req.userEmail = decodedToken.email;\n  next();\n};\n```\n\n## 7. About Endpoints\n\nThere is two different endpoint groups that are consist of auth and blog. Because Blogs are used by users, user account management must be added. Thanks to authentication, a user only can make adding, modify and deletion process to own posts.\n\nGiven below are the contents of the **server/server.js** files.\n\n```js\nconst express = require(\"express\");\n\nconst cors = require(\"./middlewares/cors\");\nconst { httpLogger } = require(\"./utils/logger\");\nconst { successHttp, errorHttp } = require(\"./middlewares/http-logger\");\nconst { err404, err500 } = require(\"./controllers/error\");\nconst authRoutes = require(\"./routes/auth\");\nconst blogRoutes = require(\"./routes/blog\");\n\nconst service = express();\n\nmodule.exports = () =\u003e {\n  service.use(express.json());\n\n  service.use(cors);\n\n  service.use(successHttp(httpLogger), errorHttp(httpLogger));\n\n  service.use(\"/auth\", authRoutes);\n\n  service.use(\"/blog\", blogRoutes);\n\n  service.use(err404, err500);\n\n  return service;\n};\n```\n\nGiven below are the contents of the **server/routes/auth.js** files.\n\n```js\nconst express = require(\"express\");\n\nconst authCont = require(\"../controllers/auth\");\nconst isAuth = require(\"../middlewares/is-auth\");\nconst isValid = require(\"../middlewares/is-valid\");\nconst imageStorage = require(\"../middlewares/image-storage\");\n\nconst router = express.Router();\n\n// Create User\nrouter.post(\"/register/\", isValid.authRegister, authCont.postRegister);\n\n// Login User\nrouter.post(\"/login/\", isValid.authLogin, authCont.postLogin);\n\n// Update User (email and name)\nrouter.patch(\"/update/\", isAuth, isValid.authUpdate, authCont.patchUpdate);\n\n// New User Password\nrouter.put(\"/password/\", isAuth, isValid.authPassword, authCont.putPassword);\n\n// Delete User\nrouter.delete(\"/user/\", isAuth, authCont.deleteUser);\n\n// Image Uplod\nrouter.post(\"/imgUpload/\", isAuth, imageStorage, authCont.postUpload);\n\nmodule.exports = router;\n```\n\nGiven below are the contents of the **server/routes/blog.js** files.\n\n```js\nconst express = require(\"express\");\n\nconst blogController = require(\"../controllers/blog\");\nconst isAuth = require(\"../middlewares/is-auth\");\nconst isValid = require(\"../middlewares/is-valid\");\n\nconst router = express.Router();\n\n// Get Recent Blogs\nrouter.get(\"/recently/\", blogController.getRecently);\n\n// Get My Blogs\nrouter.get(\"/list/\", isAuth, blogController.getList);\n\n// Post Blog\nrouter.post(\"/\", isAuth, isValid.blogPost, blogController.postBlog);\n\n// Get Blog\nrouter.get(\"/:blogId/\", blogController.getBlog);\n\n// Edit My Blog\nrouter.patch(\"/:blogId\", isAuth, isValid.blogPatch, blogController.patchBlog);\n\n// Delete Blog\nrouter.delete(\"/:blogId/\", isAuth, blogController.deleteBlog);\n\nmodule.exports = router;\n```\n\n## 8. Adding Endpoint and Internal Server Error Handling\n\n```bash\n$ touch server/controllers/error.js\n```\n\n```js\nexports.err404 = (req, res, next) =\u003e {\n  res.status(404).json({\n    message: \"Endpoint not found\",\n  });\n};\n\nexports.err500 = (error, req, res, next) =\u003e {\n  res.status(error.statusCode || 500).json({\n    message: error.message,\n    data: error.data,\n  });\n};\n```\n\n## 9. \".env\" File\n\n```\nMODE='development'\nPORT=3000\nMONGODB_URI='mongodb://test-user:newpassword@localhost:27017/api-based-blog'\nJWT_SECRET='somesupersecretsecret'\n```\n\n## 10. Test Report\n\n-----------------|---------|----------|---------|---------|-------------------\nFile             | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s \n-----------------|---------|----------|---------|---------|-------------------\nAll files        |   99.23 |     91.2 |   96.15 |   99.23 |                   \n controllers     |     100 |    89.33 |     100 |     100 |                   \n  auth.js        |     100 |     92.1 |     100 |     100 | 38,106-109        \n  blog.js        |     100 |    86.48 |     100 |     100 | 82,114,154-160    \n middlewares     |     100 |      100 |     100 |     100 |                   \n  is-auth.js     |     100 |      100 |     100 |     100 |                   \n  is-valid.js    |     100 |      100 |     100 |     100 |                   \n models/mongoose |   81.81 |      100 |       0 |   81.81 |                   \n  blog.js        |   71.42 |      100 |       0 |   71.42 | 17-18             \n  user.js        |     100 |      100 |     100 |     100 |                   \n-----------------|---------|----------|---------|---------|-------------------\n\n## License\n\nMIT Licensed.\n\nCopyright © Hidayet AYDIN 2021.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhidayet-aydin%2Fapi-based-blog-project","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhidayet-aydin%2Fapi-based-blog-project","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhidayet-aydin%2Fapi-based-blog-project/lists"}