Skip to content
This repository has been archived by the owner on Apr 12, 2022. It is now read-only.

refactor: Routes #6

Merged
merged 9 commits into from
Mar 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,12 @@ docker-compose down
├── .build # Project (TypeScript) build directory
└── src # Source files
├── constants # Constant values
├── controllers # API controllers
├── lib # 3rd party libraries (initialized/extended etc)
├── models # Database models
├── routes # API endpoints
└── scripts # Project scripts
├── scripts # Project scripts
└── utils # Project utilities
```

## Versioning
Expand Down Expand Up @@ -156,6 +159,13 @@ This section includes the issues, changes & improvements I've made, with the tho
> I used `Decimal128` as suggested in [this](https://docs.mongodb.com/manual/tutorial/model-monetary-data) official `MongoDB` document.
> For mathematical calculations in the project we can use [bignumber.js](https://www.npmjs.com/package/bignumber.js),
> to handle the precision appropriately.
- Unused/Redundant usage of `express()` & `cors()` in the `src/routes/simulator.router.ts` file.
- Redundant `/api` prefix used in the routes. I instead used a more useful `/v{major}` prefix. (`/v1`)
> Using `/v{major}` route prefix allows to possibility of deploying breaking changes without loosing backward compatibility.
- Unuseful `console.log` usage. for logging purposes, it's better use a proper logging library or service such as `Sentry`.
- Inconsistent API response format.
> Some controllers directly send the database record(s) as the response, some send them wrapped inside an object.
- No request validation was in place for any of the controllers.

### Improvements

Expand All @@ -174,12 +184,26 @@ This section includes the issues, changes & improvements I've made, with the tho
> for this purpose, I used `imports` property in `package.json` to avoid using unnecessary third-party application,
> which improves both the startup time and security, due to the fact that `Node.js` will only apply these path aliases to the current package,
> meaning no installed dependency can use these path aliases (which could cause security issues as well)
- Improvements in the `src` directory:
- Added the `src/models/index.ts` file to provide easier & cleaner access to all database models
throughout the entire project.
> I didn't use `export default` for the models, because in the `index.ts` I wanted to be able to easily use `export * from "some-model"`.
- Added the `src/constants` directory to move all constant values there instead of being scattered all over the project.
> This ensures that the project is using the same consistent values everywhere.
> `MODEL` & `COLLECTION` constants are also added, due to the fact that they can be useful in scenarios such as `$lookup` aggregation stages.
> Also In case of need to change the said values, it just needs to be updated in one places only.
- Added the `src/routes/index.ts` file to provide cleaner api endpoint management.
> I exported the routes as `default` in this case, due to only having one job, which is exporting the express `Router`.
- Renamed the router filenames. The `.router` part of the name was redundant,
since they're already under the `routes` directory.
- Moved the controllers to the `controllers` directory & each controller to be in a separate file.
> This makes code cleaner and easier to maintain. not the directory `routes` only manages the routing &
> the `controllers` directory manages route behaviors.
> By putting controllers in separate files, it will become easier to detect which dependencies are used in which controller
> and the number of controllers won't affect the readability of the code, thus easier to improve, debug & maintain.
- Added the `wrapController` utility.
> `express` doesn't catch async errors properly, so I added this utility to wrap the controllers before passing them
> to the `Router` instance.

<!-- Footnotes -->

Expand Down
117 changes: 117 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
},
"dependencies": {
"body-parser": "1.19.2",
"celebrate": "15.0.1",
"cors": "2.8.5",
"dotenv": "16.0.0",
"express": "4.17.3",
Expand Down
11 changes: 5 additions & 6 deletions src/api.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { CORS_ORIGINS, DBURL, PORT } from "#src/config.js";
import { router as favoriteRouter } from "#src/routes/favorite.router.js";
import { router as profileRouter } from "#src/routes/profile.router.js";
import { router as simulatorRouter } from "#src/routes/simulator.router.js";
import routes from "#src/routes/index.js";
import bodyParser from "body-parser";
import { errors } from "celebrate";
import cors from "cors";
import express from "express";
import mongoose from "mongoose";
Expand All @@ -17,9 +16,9 @@ const app = express();
app.use(cors({ origin: CORS_ORIGINS }));
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(favoriteRouter);
app.use(profileRouter);
app.use(simulatorRouter);
app.use(routes);

app.use(errors());

app.listen(PORT, () =>
console.log(`✅ Ready on port http://localhost:${PORT}`),
Expand Down
1 change: 1 addition & 0 deletions src/controllers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./v1/index.js";
1 change: 1 addition & 0 deletions src/controllers/v1/favorites/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./list.js";
26 changes: 26 additions & 0 deletions src/controllers/v1/favorites/list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Joi } from "#src/lib/index.js";
import { Favorite, FavoriteI } from "#src/models/index.js";
import { celebrate, Segments } from "celebrate";
import { RequestHandler } from "express";
import { FilterQuery } from "mongoose";

export const listFavoritesV1: RequestHandler[] = [
celebrate({
[Segments.PARAMS]: Joi.object()
.keys({
profile_id: Joi.string().objectId().required(),
})
.required(),
}),
async (req, res) => {
const { profile_id } = req.params;

const query: FilterQuery<FavoriteI> = {};

if (profile_id != null) query.profile_id = profile_id;

const favorites = await Favorite.find(query).lean();

res.json({ favorites });
},
];
3 changes: 3 additions & 0 deletions src/controllers/v1/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "./favorites/index.js";
export * from "./profiles/index.js";
export * from "./simulators/index.js";
2 changes: 2 additions & 0 deletions src/controllers/v1/profiles/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./list.js";
export * from "./post.js";
10 changes: 10 additions & 0 deletions src/controllers/v1/profiles/list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Profile } from "#src/models/index.js";
import { RequestHandler } from "express";

export const listProfilesV1: RequestHandler[] = [
async (req, res) => {
const profiles = await Profile.find().lean();

res.json({ profiles });
},
];
29 changes: 29 additions & 0 deletions src/controllers/v1/profiles/post.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Joi } from "#src/lib/index.js";
import { Profile } from "#src/models/index.js";
import { celebrate, Segments } from "celebrate";
import { RequestHandler } from "express";

export const postProfileV1: RequestHandler[] = [
celebrate({
[Segments.BODY]: Joi.object()
.keys({
email: Joi.string().email().required(),
name: Joi.string().min(3).required(),
nickname: Joi.string().min(3).required(),
})
.required(),
}),
async (req, res) => {
const { email, name, nickname } = req.body;

let profile = await Profile.findOne({
$or: [{ email }, { nickname }],
});

if (!profile) {
profile = await Profile.create({ name, email, nickname });
}

res.json({ profile });
},
];
2 changes: 2 additions & 0 deletions src/controllers/v1/simulators/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./list.js";
export * from "./post.js";
26 changes: 26 additions & 0 deletions src/controllers/v1/simulators/list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Joi } from "#src/lib/index.js";
import { Simulator, SimulatorI } from "#src/models/index.js";
import { celebrate, Segments } from "celebrate";
import { RequestHandler } from "express";
import { FilterQuery } from "mongoose";

export const listSimulatorsV1: RequestHandler[] = [
celebrate({
[Segments.PARAMS]: Joi.object()
.keys({
profile_id: Joi.string().objectId().required(),
})
.required(),
}),
async (req, res) => {
const { profile_id } = req.params;

const query: FilterQuery<SimulatorI> = {};

if (profile_id != null) query.profile_id = profile_id;

const simulators = await Simulator.find(query).lean();

res.json({ simulators });
},
];
Loading