{"id":17567283,"url":"https://github.com/canon20paul/jobsterapi-frontend","last_synced_at":"2026-04-09T02:31:00.230Z","repository":{"id":136641115,"uuid":"566758610","full_name":"canon20paul/JobsterAPI-FrontEnd","owner":"canon20paul","description":"The JobsterAPI is built from Modification to a copy of a JobsAPI project to Link it to a React FrontEnd pre built in a client file is a copy of jobs-api final project, just with additional data.Nodejs + Express +React deployed to Render","archived":false,"fork":false,"pushed_at":"2022-12-07T17:28:57.000Z","size":2510,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-07T00:38:01.421Z","etag":null,"topics":["api","express","jwt","mongodb-atlas","mongoose","nodejs","react","render"],"latest_commit_sha":null,"homepage":"https://node-course-jobster-api-10vu.onrender.com","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/canon20paul.png","metadata":{"files":{"readme":"README.MD","changelog":null,"contributing":null,"funding":null,"license":null,"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":"2022-11-16T11:06:55.000Z","updated_at":"2022-12-09T22:20:19.000Z","dependencies_parsed_at":"2024-10-22T13:37:18.119Z","dependency_job_id":null,"html_url":"https://github.com/canon20paul/JobsterAPI-FrontEnd","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/canon20paul/JobsterAPI-FrontEnd","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/canon20paul%2FJobsterAPI-FrontEnd","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/canon20paul%2FJobsterAPI-FrontEnd/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/canon20paul%2FJobsterAPI-FrontEnd/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/canon20paul%2FJobsterAPI-FrontEnd/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/canon20paul","download_url":"https://codeload.github.com/canon20paul/JobsterAPI-FrontEnd/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/canon20paul%2FJobsterAPI-FrontEnd/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31582545,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-08T14:31:17.711Z","status":"online","status_checked_at":"2026-04-09T02:00:06.848Z","response_time":112,"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":["api","express","jwt","mongodb-atlas","mongoose","nodejs","react","render"],"created_at":"2024-10-21T15:43:19.131Z","updated_at":"2026-04-09T02:31:00.214Z","avatar_url":"https://github.com/canon20paul.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# **Jobster API + FrontEnd**\n[Deployed ***LINK*** at Render.com](https://node-course-jobster-api-10vu.onrender.com/)\n\n### Description\n\nThe JobsterAPI is built from Modification to a copy of a JobsAPI project to Link it to a React FrontEnd pre built in a client file with additional data.\n\n### Delieverables\n\n- Profile Interface to Manage userdetails\n- Add job interface to add and remove jobs\n- Alljobs interface to search and display jobs\n- Stats interface to provide relevant job Application processes\n\n\n\n#### Setup\n\n- navigate to 06.5-jobster-api/starter\n- install dependencies\n\n```sh\nnpm install\n```\n\n- create .env and provide correct values\n- you can copy from previous project (just change the DB name)\n\n.env\n\n```js\nMONGO_URI=\nJWT_SECRET=\nJWT_LIFETIME=\n```\n\n- start the project\n\n```sh\nnpm start\n```\n\n- you should see \"Server is listening ....\" text\n\n#### Spring Cleaning\n\n##### Remove Swagger\n\n- delete swagger.yaml file\n- remove these lines of code\n- delete swagger.yaml\n  app.js\n\n```js\nconst swaggerUI = require('swagger-ui-express');\nconst YAML = require('yamljs');\nconst swaggerDocument = YAML.load('./swagger.yaml');\n\napp.get('/', (req, res) =\u003e {\n  res.send('\u003ch1\u003eJobs API\u003c/h1\u003e\u003ca href=\"/api-docs\"\u003eDocumentation\u003c/a\u003e');\n});\napp.use('/api-docs', swaggerUI.serve, swaggerUI.setup(swaggerDocument));\n```\n\n#### Remove API Limiter\n\n- remove these lines of code\n\napp.js\n\n```js\nconst rateLimiter = require('express-rate-limit');\n\napp.set('trust proxy', 1);\napp.use(\n  rateLimiter({\n    windowMs: 15 * 60 * 1000, // 15 minutes\n    max: 100, // limit each IP to 100 requests per windowMs\n  })\n);\n```\n\n#### Remove CORS\n\n- we don't want external JS apps to access our API (only our front-end)\n- remove these lines of code\n\n```js\nconst cors = require('cors');\napp.use(cors());\n```\n\n#### Package.json\n\n- add \"dev\" script with nodemon\n- change engines to current version (in my case 16)\n\npackage.json\n\n```js\n\n\"scripts\": {\n    \"start\": \"node app.js\",\n    \"dev\": \"nodemon app.js\"\n  },\n\n\"engines\": {\n    \"node\": \"16.x\"\n  }\n```\n\n- restart server with \"npm run dev\"\n\n##### Client Folder\n\n- let's explore client folder\n- open client folder\n- it's a react app built with CRA\n- it's the same as in my React Course (JOBSTER APP),\n  just base url points to our current server (instead of heroku app)\n\nutils/axios.js\n\n```js\nconst customFetch = axios.create({\n  baseURL: '/api/v1',\n});\n```\n\n- notice the build folder (production ready application)\n- in CRA we can create build folder by running \"npm run build\"\n- that's the one we will use for our front-end\n\n##### Setup Front-End\n\n- require \"path\" module\n- setup express static (as first middleware)\n  to serve static assets from client/build\n- so now instead of public folder we are using client/build\n\napp.js\n\n```js\nconst path = require('path');\n\napp.use(express.static(path.resolve(__dirname, './client/build')));\n\n// place as first middleware\napp.use(express.json());\napp.use(helmet());\napp.use(cors());\napp.use(xss());\n```\n\n- serve index.html for all routes (apart from API)\n- front-end routes pick's it up from there\n\n```js\napp.use('/api/v1/auth', authRouter);\napp.use('/api/v1/jobs', authenticateUser, jobsRouter);\n\n// serve index.html\napp.get('*', (req, res) =\u003e {\n  res.sendFile(path.resolve(__dirname, './client/build', 'index.html'));\n});\n\napp.use(notFoundMiddleware);\napp.use(errorHandlerMiddleware);\n```\n\n- navigate to localhost:5000\n- clear local storage (if necessary)\n\n#### Modify User Model\n\n- add following properties\n\nmodels/User.js\n\n```js\nlastName: {\n    type: String,\n    trim: true,\n    maxlength: 20,\n    default: 'lastName',\n  },\n  location: {\n    type: String,\n    trim: true,\n    maxlength: 20,\n    default: 'my city',\n  },\n\n```\n\n#### Modify Response in Register and Login\n\n- change the response structure in\n  register and login controllers (just keep old StatusCodes)\n\ncontrollers/auth.js\n\n```js\nres.status(StatusCodes.CREATED).json({\n  user: {\n    email: user.email,\n    lastName: user.lastName,\n    location: user.location,\n    name: user.name,\n    token,\n  },\n});\n```\n\n- Login and Register Works Now :):):)\n\n#### Create Test User\n\n- test front-end request\n- in postman or front-end\n- make sure email and password are the same (or change the front-end)\n\n```js\n{\n    \"name\":\"demo user\",\n    \"email\":\"testUser@test.com\",\n    \"password\":\"secret\"\n}\n```\n\n- navigate to client/src/pages/Register.js\n- make sure email and password match your test user\n\n```js\n\u003cbutton\n  type='button'\n  className='btn btn-block btn-hipster'\n  disabled={isLoading}\n  onClick={() =\u003e\n  dispatch(loginUser({ email: 'testUser@test.com', password: 'secret' }))}\u003e\n\n```\n\n#### Update User Functionality\n\n- import authenticateUser middleware\n- setup updateUser route (protected route)\n\nroutes/auth.js\n\n```js\nconst express = require('express');\nconst router = express.Router();\nconst authenticateUser = require('../middleware/authentication');\nconst { register, login, updateUser } = require('../controllers/auth');\nrouter.post('/register', register);\nrouter.post('/login', login);\nrouter.patch('/updateUser', authenticateUser, updateUser);\n\nmodule.exports = router;\n```\n\n- create updateUser controller\n- setup export\n\ncontrollers/auth.js\n\n```js\nconst updateUser = async (req, res) =\u003e {\n  console.log(req.user);\n  console.log(req.body);\n};\n```\n\n- complete updateUser\n\ncontrollers/auth.js\n\n```js\nconst updateUser = async (req, res) =\u003e {\n  const { email, name, lastName, location } = req.body;\n  if (!email || !name || !lastName || !location) {\n    throw new BadRequestError('Please provide all values');\n  }\n  const user = await User.findOne({ _id: req.user.userId });\n\n  user.email = email;\n  user.name = name;\n  user.lastName = lastName;\n  user.location = location;\n\n  await user.save();\n\n  const token = user.createJWT();\n\n  res.status(StatusCodes.OK).json({\n    user: {\n      email: user.email,\n      lastName: user.lastName,\n      location: user.location,\n      name: user.name,\n      token,\n    },\n  });\n};\n```\n\n- Why New Token?\n\n#### GOTCHA\n\n- this.modifiedPaths();\n\n```js\nUserSchema.pre('save', async function () {\n  if (!this.isModified('password')) return;\n  const salt = await bcrypt.genSalt(10);\n  this.password = await bcrypt.hash(this.password, salt);\n});\n```\n\n#### Modify Job Model\n\n- add following properties\n\nmodels/Job.js\n\n```js\njobType: {\n      type: String,\n      enum: ['full-time', 'part-time', 'remote', 'internship'],\n      default: 'full-time',\n    },\n    jobLocation: {\n      type: String,\n      default: 'my city',\n      required: true,\n    },\n```\n\n- default property just for fun, values will be provided by front-end\n\n#### Setup Mock Data\n\n- [Mockaroo](https://www.mockaroo.com/)\n- create mock-data.json (root)\n- provide test user id\n\n#### Populate DB\n\n- create populate.js\n\npopulate.js\n\n```js\nrequire('dotenv').config();\n\nconst mockData = require('./mock-data.json');\n\nconst Job = require('./models/Job');\nconst connectDB = require('./db/connect');\n\nconst start = async () =\u003e {\n  try {\n    await connectDB(process.env.MONGO_URI);\n\n    await Job.create(mockData);\n    console.log('Success!!!');\n    process.exit(0);\n  } catch (error) {\n    console.log(error);\n    process.exit(1);\n  }\n};\n\nstart();\n```\n\n#### Modify Get All Jobs\n\n- latest mongoose version change\n\ncontrollers/jobs.js\n\n```js\nconst getAllJobs = async (req, res) =\u003e {\n  const { search, status, jobType, sort } = req.query;\n\n  // protected route\n  const queryObject = {\n    createdBy: req.user.userId,\n  };\n\n  if (search) {\n    queryObject.position = { $regex: search, $options: 'i' };\n  }\n  // add stuff based on condition\n\n  if (status \u0026\u0026 status !== 'all') {\n    queryObject.status = status;\n  }\n  if (jobType \u0026\u0026 jobType !== 'all') {\n    queryObject.jobType = jobType;\n  }\n\n  // NO AWAIT\n\n  let result = Job.find(queryObject);\n\n  // chain sort conditions\n\n  if (sort === 'latest') {\n    result = result.sort('-createdAt');\n  }\n  if (sort === 'oldest') {\n    result = result.sort('createdAt');\n  }\n  if (sort === 'a-z') {\n    result = result.sort('position');\n  }\n  if (sort === 'z-a') {\n    result = result.sort('-position');\n  }\n\n  //\n\n  // setup pagination\n  const page = Number(req.query.page) || 1;\n  const limit = Number(req.query.limit) || 10;\n  const skip = (page - 1) * limit;\n\n  result = result.skip(skip).limit(limit);\n\n  const jobs = await result;\n\n  const totalJobs = await Job.countDocuments(queryObject);\n  const numOfPages = Math.ceil(totalJobs / limit);\n\n  res.status(StatusCodes.OK).json({ jobs, totalJobs, numOfPages });\n};\n```\n\n#### Make Test User Read-Only\n\nmiddleware/authentication.js\n\n```js\nconst payload = jwt.verify(token, process.env.JWT_SECRET);\nconst testUser = payload.userId === '62eff8bcdb9af70b4155349d';\nreq.user = { userId: payload.userId, testUser };\n```\n\n- create testingUser in middleware\n\nmiddleware/testUser\n\n```js\nconst { BadRequestError } = require('../errors');\n\nconst testUser = (req, res, next) =\u003e {\n  if (req.user.testUser) {\n    throw new BadRequestError('Test User. Read Only!');\n  }\n  next();\n};\n\nmodule.exports = testUser;\n```\n\n- add to auth routes (updateUser)\n\n```js\nconst express = require('express');\nconst router = express.Router();\nconst authenticateUser = require('../middleware/authentication');\nconst testUser = require('../middleware/testUser');\nconst { register, login, updateUser } = require('../controllers/auth');\n\nrouter.post('/register', register);\nrouter.post('/login', login);\nrouter.patch('/updateUser', authenticateUser, testUser, updateUser);\n\nmodule.exports = router;\n```\n\n- add to job routes (createJob, updateJob, deleteJob)\n\nroutes/jobs.js\n\n```js\nconst express = require('express');\n\nconst router = express.Router();\nconst {\n  createJob,\n  deleteJob,\n  getAllJobs,\n  updateJob,\n  getJob,\n  showStats,\n} = require('../controllers/jobs');\nconst testUser = require('../middleware/testUser');\n\nrouter.route('/').post(testUser, createJob).get(getAllJobs);\nrouter.route('/stats').get(showStats);\nrouter\n  .route('/:id')\n  .get(getJob)\n  .delete(testUser, deleteJob)\n  .patch(testUser, updateJob);\n\nmodule.exports = router;\n```\n\n#### API Limiter\n\nroutes/auth.js\n\n```js\nconst express = require('express');\nconst router = express.Router();\nconst authenticateUser = require('../middleware/authentication');\nconst testUser = require('../middleware/testUser');\nconst { register, login, updateUser } = require('../controllers/auth');\n\nconst rateLimiter = require('express-rate-limit');\nconst apiLimiter = rateLimiter({\n  windowMs: 15 * 60 * 1000, // 15 minutes\n  max: 10,\n  message: {\n    msg: 'Too many requests from this IP, please try again after 15 minutes',\n  },\n});\n\nrouter.post('/register', apiLimiter, register);\nrouter.post('/login', apiLimiter, login);\nrouter.patch('/updateUser', authenticateUser, testUser, updateUser);\n\nmodule.exports = router;\n```\n\napp.js\n\n```js\napp.set('trust proxy', 1);\n\napp.use(express.static(path.resolve(__dirname, './client/build')));\n```\n\n#### Setup Stats Route\n\ncontrollers/jobs\n\n```js\nconst showStats = (req, res) =\u003e {\n  res\n    .status(StatusCodes.OK)\n    .json({ defaultStats: {}, monthlyApplications: [] });\n};\n\nmodule.exports = {\n  createJob,\n  deleteJob,\n  getAllJobs,\n  updateJob,\n  getJob,\n  showStats,\n};\n```\n\nroutes/jobs.js\n\n```js\nconst express = require('express');\n\nconst router = express.Router();\nconst {\n  createJob,\n  deleteJob,\n  getAllJobs,\n  updateJob,\n  getJob,\n  showStats,\n} = require('../controllers/jobs');\n\nrouter.route('/').post(createJob).get(getAllJobs);\nrouter.route('/stats').get(showStats);\nrouter.route('/:id').get(getJob).delete(deleteJob).patch(updateJob);\n\nmodule.exports = router;\n```\n\n- npm install moment\n- import mongoose and moment\n\ncontrollers/jobs\n\n```js\nconst mongoose = require('mongoose');\nconst moment = require('moment');\n```\n\n- [Javascript Nuggets - Reduce Basics](https://youtu.be/3WkW9nrS2mw)\n- [Javascript Nuggets - Reduce (object example)](https://youtu.be/5BFkp8JjLEY)\n\ncontrollers/jobs\n\n```js\nconst showStats = async (req, res) =\u003e {\n  let stats = await Job.aggregate([\n    { $match: { createdBy: mongoose.Types.ObjectId(req.user.userId) } },\n    { $group: { _id: '$status', count: { $sum: 1 } } },\n  ]);\n\n  stats = stats.reduce((acc, curr) =\u003e {\n    const { _id: title, count } = curr;\n    acc[title] = count;\n    return acc;\n  }, {});\n\n  const defaultStats = {\n    pending: stats.pending || 0,\n    interview: stats.interview || 0,\n    declined: stats.declined || 0,\n  };\n\n  let monthlyApplications = await Job.aggregate([\n    { $match: { createdBy: mongoose.Types.ObjectId(req.user.userId) } },\n    {\n      $group: {\n        _id: { year: { $year: '$createdAt' }, month: { $month: '$createdAt' } },\n        count: { $sum: 1 },\n      },\n    },\n    { $sort: { '_id.year': -1, '_id.month': -1 } },\n    { $limit: 6 },\n  ]);\n  monthlyApplications = monthlyApplications\n    .map((item) =\u003e {\n      const {\n        _id: { year, month },\n        count,\n      } = item;\n      const date = moment()\n        .month(month - 1)\n        .year(year)\n        .format('MMM Y');\n      return { date, count };\n    })\n    .reverse();\n\n  res.status(StatusCodes.OK).json({ defaultStats, monthlyApplications });\n};\n```\n\n#### Deploy\n\n- move starter to desktop\n- remove existing .git repo\n- heroku login \"heroku login\"\n- git init\n- git add .\n- git commit -m \"commit message\"\n- heroku create nameOfTheApp\n- setup .env variables in heroku dashboard\n- git push heroku master (or main)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcanon20paul%2Fjobsterapi-frontend","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcanon20paul%2Fjobsterapi-frontend","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcanon20paul%2Fjobsterapi-frontend/lists"}