Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/canon20paul/jobsterapi-frontend
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
https://github.com/canon20paul/jobsterapi-frontend
api express jwt mongodb-atlas mongoose nodejs react render
Last synced: 10 days ago
JSON representation
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
- Host: GitHub
- URL: https://github.com/canon20paul/jobsterapi-frontend
- Owner: canon20paul
- Created: 2022-11-16T11:06:55.000Z (about 2 years ago)
- Default Branch: main
- Last Pushed: 2022-12-07T17:28:57.000Z (about 2 years ago)
- Last Synced: 2024-12-09T19:27:17.369Z (20 days ago)
- Topics: api, express, jwt, mongodb-atlas, mongoose, nodejs, react, render
- Language: JavaScript
- Homepage: https://node-course-jobster-api-10vu.onrender.com
- Size: 2.39 MB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.MD
Awesome Lists containing this project
README
# **Jobster API + FrontEnd**
[Deployed ***LINK*** at Render.com](https://node-course-jobster-api-10vu.onrender.com/)### 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 with additional data.
### Delieverables
- Profile Interface to Manage userdetails
- Add job interface to add and remove jobs
- Alljobs interface to search and display jobs
- Stats interface to provide relevant job Application processes#### Setup
- navigate to 06.5-jobster-api/starter
- install dependencies```sh
npm install
```- create .env and provide correct values
- you can copy from previous project (just change the DB name).env
```js
MONGO_URI=
JWT_SECRET=
JWT_LIFETIME=
```- start the project
```sh
npm start
```- you should see "Server is listening ...." text
#### Spring Cleaning
##### Remove Swagger
- delete swagger.yaml file
- remove these lines of code
- delete swagger.yaml
app.js```js
const swaggerUI = require('swagger-ui-express');
const YAML = require('yamljs');
const swaggerDocument = YAML.load('./swagger.yaml');app.get('/', (req, res) => {
res.send('Jobs API
Documentation');
});
app.use('/api-docs', swaggerUI.serve, swaggerUI.setup(swaggerDocument));
```#### Remove API Limiter
- remove these lines of code
app.js
```js
const rateLimiter = require('express-rate-limit');app.set('trust proxy', 1);
app.use(
rateLimiter({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
})
);
```#### Remove CORS
- we don't want external JS apps to access our API (only our front-end)
- remove these lines of code```js
const cors = require('cors');
app.use(cors());
```#### Package.json
- add "dev" script with nodemon
- change engines to current version (in my case 16)package.json
```js
"scripts": {
"start": "node app.js",
"dev": "nodemon app.js"
},"engines": {
"node": "16.x"
}
```- restart server with "npm run dev"
##### Client Folder
- let's explore client folder
- open client folder
- it's a react app built with CRA
- it's the same as in my React Course (JOBSTER APP),
just base url points to our current server (instead of heroku app)utils/axios.js
```js
const customFetch = axios.create({
baseURL: '/api/v1',
});
```- notice the build folder (production ready application)
- in CRA we can create build folder by running "npm run build"
- that's the one we will use for our front-end##### Setup Front-End
- require "path" module
- setup express static (as first middleware)
to serve static assets from client/build
- so now instead of public folder we are using client/buildapp.js
```js
const path = require('path');app.use(express.static(path.resolve(__dirname, './client/build')));
// place as first middleware
app.use(express.json());
app.use(helmet());
app.use(cors());
app.use(xss());
```- serve index.html for all routes (apart from API)
- front-end routes pick's it up from there```js
app.use('/api/v1/auth', authRouter);
app.use('/api/v1/jobs', authenticateUser, jobsRouter);// serve index.html
app.get('*', (req, res) => {
res.sendFile(path.resolve(__dirname, './client/build', 'index.html'));
});app.use(notFoundMiddleware);
app.use(errorHandlerMiddleware);
```- navigate to localhost:5000
- clear local storage (if necessary)#### Modify User Model
- add following properties
models/User.js
```js
lastName: {
type: String,
trim: true,
maxlength: 20,
default: 'lastName',
},
location: {
type: String,
trim: true,
maxlength: 20,
default: 'my city',
},```
#### Modify Response in Register and Login
- change the response structure in
register and login controllers (just keep old StatusCodes)controllers/auth.js
```js
res.status(StatusCodes.CREATED).json({
user: {
email: user.email,
lastName: user.lastName,
location: user.location,
name: user.name,
token,
},
});
```- Login and Register Works Now :):):)
#### Create Test User
- test front-end request
- in postman or front-end
- make sure email and password are the same (or change the front-end)```js
{
"name":"demo user",
"email":"[email protected]",
"password":"secret"
}
```- navigate to client/src/pages/Register.js
- make sure email and password match your test user```js
dispatch(loginUser({ email: '[email protected]', password: 'secret' }))}>
```
#### Update User Functionality
- import authenticateUser middleware
- setup updateUser route (protected route)routes/auth.js
```js
const express = require('express');
const router = express.Router();
const authenticateUser = require('../middleware/authentication');
const { register, login, updateUser } = require('../controllers/auth');
router.post('/register', register);
router.post('/login', login);
router.patch('/updateUser', authenticateUser, updateUser);module.exports = router;
```- create updateUser controller
- setup exportcontrollers/auth.js
```js
const updateUser = async (req, res) => {
console.log(req.user);
console.log(req.body);
};
```- complete updateUser
controllers/auth.js
```js
const updateUser = async (req, res) => {
const { email, name, lastName, location } = req.body;
if (!email || !name || !lastName || !location) {
throw new BadRequestError('Please provide all values');
}
const user = await User.findOne({ _id: req.user.userId });user.email = email;
user.name = name;
user.lastName = lastName;
user.location = location;await user.save();
const token = user.createJWT();
res.status(StatusCodes.OK).json({
user: {
email: user.email,
lastName: user.lastName,
location: user.location,
name: user.name,
token,
},
});
};
```- Why New Token?
#### GOTCHA
- this.modifiedPaths();
```js
UserSchema.pre('save', async function () {
if (!this.isModified('password')) return;
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
});
```#### Modify Job Model
- add following properties
models/Job.js
```js
jobType: {
type: String,
enum: ['full-time', 'part-time', 'remote', 'internship'],
default: 'full-time',
},
jobLocation: {
type: String,
default: 'my city',
required: true,
},
```- default property just for fun, values will be provided by front-end
#### Setup Mock Data
- [Mockaroo](https://www.mockaroo.com/)
- create mock-data.json (root)
- provide test user id#### Populate DB
- create populate.js
populate.js
```js
require('dotenv').config();const mockData = require('./mock-data.json');
const Job = require('./models/Job');
const connectDB = require('./db/connect');const start = async () => {
try {
await connectDB(process.env.MONGO_URI);await Job.create(mockData);
console.log('Success!!!');
process.exit(0);
} catch (error) {
console.log(error);
process.exit(1);
}
};start();
```#### Modify Get All Jobs
- latest mongoose version change
controllers/jobs.js
```js
const getAllJobs = async (req, res) => {
const { search, status, jobType, sort } = req.query;// protected route
const queryObject = {
createdBy: req.user.userId,
};if (search) {
queryObject.position = { $regex: search, $options: 'i' };
}
// add stuff based on conditionif (status && status !== 'all') {
queryObject.status = status;
}
if (jobType && jobType !== 'all') {
queryObject.jobType = jobType;
}// NO AWAIT
let result = Job.find(queryObject);
// chain sort conditions
if (sort === 'latest') {
result = result.sort('-createdAt');
}
if (sort === 'oldest') {
result = result.sort('createdAt');
}
if (sort === 'a-z') {
result = result.sort('position');
}
if (sort === 'z-a') {
result = result.sort('-position');
}//
// setup pagination
const page = Number(req.query.page) || 1;
const limit = Number(req.query.limit) || 10;
const skip = (page - 1) * limit;result = result.skip(skip).limit(limit);
const jobs = await result;
const totalJobs = await Job.countDocuments(queryObject);
const numOfPages = Math.ceil(totalJobs / limit);res.status(StatusCodes.OK).json({ jobs, totalJobs, numOfPages });
};
```#### Make Test User Read-Only
middleware/authentication.js
```js
const payload = jwt.verify(token, process.env.JWT_SECRET);
const testUser = payload.userId === '62eff8bcdb9af70b4155349d';
req.user = { userId: payload.userId, testUser };
```- create testingUser in middleware
middleware/testUser
```js
const { BadRequestError } = require('../errors');const testUser = (req, res, next) => {
if (req.user.testUser) {
throw new BadRequestError('Test User. Read Only!');
}
next();
};module.exports = testUser;
```- add to auth routes (updateUser)
```js
const express = require('express');
const router = express.Router();
const authenticateUser = require('../middleware/authentication');
const testUser = require('../middleware/testUser');
const { register, login, updateUser } = require('../controllers/auth');router.post('/register', register);
router.post('/login', login);
router.patch('/updateUser', authenticateUser, testUser, updateUser);module.exports = router;
```- add to job routes (createJob, updateJob, deleteJob)
routes/jobs.js
```js
const express = require('express');const router = express.Router();
const {
createJob,
deleteJob,
getAllJobs,
updateJob,
getJob,
showStats,
} = require('../controllers/jobs');
const testUser = require('../middleware/testUser');router.route('/').post(testUser, createJob).get(getAllJobs);
router.route('/stats').get(showStats);
router
.route('/:id')
.get(getJob)
.delete(testUser, deleteJob)
.patch(testUser, updateJob);module.exports = router;
```#### API Limiter
routes/auth.js
```js
const express = require('express');
const router = express.Router();
const authenticateUser = require('../middleware/authentication');
const testUser = require('../middleware/testUser');
const { register, login, updateUser } = require('../controllers/auth');const rateLimiter = require('express-rate-limit');
const apiLimiter = rateLimiter({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 10,
message: {
msg: 'Too many requests from this IP, please try again after 15 minutes',
},
});router.post('/register', apiLimiter, register);
router.post('/login', apiLimiter, login);
router.patch('/updateUser', authenticateUser, testUser, updateUser);module.exports = router;
```app.js
```js
app.set('trust proxy', 1);app.use(express.static(path.resolve(__dirname, './client/build')));
```#### Setup Stats Route
controllers/jobs
```js
const showStats = (req, res) => {
res
.status(StatusCodes.OK)
.json({ defaultStats: {}, monthlyApplications: [] });
};module.exports = {
createJob,
deleteJob,
getAllJobs,
updateJob,
getJob,
showStats,
};
```routes/jobs.js
```js
const express = require('express');const router = express.Router();
const {
createJob,
deleteJob,
getAllJobs,
updateJob,
getJob,
showStats,
} = require('../controllers/jobs');router.route('/').post(createJob).get(getAllJobs);
router.route('/stats').get(showStats);
router.route('/:id').get(getJob).delete(deleteJob).patch(updateJob);module.exports = router;
```- npm install moment
- import mongoose and momentcontrollers/jobs
```js
const mongoose = require('mongoose');
const moment = require('moment');
```- [Javascript Nuggets - Reduce Basics](https://youtu.be/3WkW9nrS2mw)
- [Javascript Nuggets - Reduce (object example)](https://youtu.be/5BFkp8JjLEY)controllers/jobs
```js
const showStats = async (req, res) => {
let stats = await Job.aggregate([
{ $match: { createdBy: mongoose.Types.ObjectId(req.user.userId) } },
{ $group: { _id: '$status', count: { $sum: 1 } } },
]);stats = stats.reduce((acc, curr) => {
const { _id: title, count } = curr;
acc[title] = count;
return acc;
}, {});const defaultStats = {
pending: stats.pending || 0,
interview: stats.interview || 0,
declined: stats.declined || 0,
};let monthlyApplications = await Job.aggregate([
{ $match: { createdBy: mongoose.Types.ObjectId(req.user.userId) } },
{
$group: {
_id: { year: { $year: '$createdAt' }, month: { $month: '$createdAt' } },
count: { $sum: 1 },
},
},
{ $sort: { '_id.year': -1, '_id.month': -1 } },
{ $limit: 6 },
]);
monthlyApplications = monthlyApplications
.map((item) => {
const {
_id: { year, month },
count,
} = item;
const date = moment()
.month(month - 1)
.year(year)
.format('MMM Y');
return { date, count };
})
.reverse();res.status(StatusCodes.OK).json({ defaultStats, monthlyApplications });
};
```#### Deploy
- move starter to desktop
- remove existing .git repo
- heroku login "heroku login"
- git init
- git add .
- git commit -m "commit message"
- heroku create nameOfTheApp
- setup .env variables in heroku dashboard
- git push heroku master (or main)