In this code base, the genuis of the application has been contributed by the following engineers:
- huytran17
- tungnt1405
Real Artist sign their work
Database => mongoose
File storage => FS, AWS S3
Cache management => Redis
Virtual platform => Docker
JWT issuer => jsonwebtoken npm
Authentication => passport.js
API validator => validatorjs
Backend server => NodeJS, ExpressJS
Backend logs => Winston
Unit testing => Jest
Dashboard => NuxtJS
UI/UX => Vuetify
2FA => Google Authenticator
Optimizer => Gulp, Webpack
Web socket => socket.io
Read more at Clean Coder Blog
- Independent of Frameworks
- Testability
- Independent of UI
- Independent of Database
- Independent of External
- Dependencies can only point inwards. That means inner layer should not know/calls outer layer
- Entities: Contain enterprise business model/object
- Use Cases: Contain application business rules/ logic
- Interface Adapter: Contains a set of adapters that convert data from entities/use-case layer to external dependencies such as DB or Web/HTTP
- Frameworks/ Driver: Compose of frameworks and tools (DB, Web Frameworks)
- Communication between layers should be resolve via Dependency Inversion
- Create interface/type for an injectable class/function
// <folder>/injectable.ts
export type IInjectable = (params: any) => Promise<string>;
- Create a factory function and export it
export default function makeInjectable({
//Factory function
dependencies1,
dependencies2,
}: {
dependencies1: IDependencies1, // Inject dependencies
dependencies2: IDependencies2,
}): IInjectable {
// injectable must implement IInjectable
return function injectable({
params1,
}: {
params1: Params1Type, // Pass in parameters
}): string {};
}
- Initialize the function by calling the factory in index.ts file
// <folder>/index.ts
import makeInjectable, { IInjectable } from "./injectable.ts"; // Import type and factory
const injectable = makeInjectable({ dependencies1 }); // Pass in instances of dependencies
export default Object.freeze({
injectable,
});
export { injectable };
Bad:
export default function makeSomeController() {
return async function getSomeController() {};
}
Good
// Controller
export default function makeGetSomeController() {
return async function getSomeController() {
// make function and internal function must be the same name
};
}
Bad:
import getExample from "../../../use-cases/get-example";
export default function makeGetExampleController() {
return async function getExampleController() {
await getExample(); // Directly call use-case
};
}
Good
// Controller
import { IGetExample } from "../../../use-cases/get-example"; // Import interface
export default function makeGetBlogController({
getExample,
}: {
getExample: IGetExample, // Inject dependencies
}) {
return async function getBlogController() {
await getExample(); // Directly call use-case
};
}
*Bad
export default function makeGetExampleController() {
return async function getExampleController(
httpRequest: {
context: {
validated: null,
},
},
someDeps: any
) {
someDeps();
};
}
*Good
export default function makeGetExampleController({
someDep,
}: {
someDep: ISomeDep, // Pass in dependency
}) {
return async function getExampleController(httpRequest: {
context: {
validated: null,
},
}) {};
}
Bad:
function controllerFunction(httpRequest) {
const param = httpRequest.context.validated.param;
return {
statusCode: HttpStatusCode.OK,
};
}
Good:
function controllerFunction(httpRequest) {
try {
const param = httpRequest.context.validated.param;
return {
statusCode: HttpStatusCode.OK,
};
} catch (error) {
return {
headers: {
"Content-Type": "application/json",
},
statusCode: error.status.code,
body: {
error: error.message,
},
};
}
}
For request validations, create a file in validations folder. Reference src/controllers/user/blog/validators/get-blog.ts
controller/<module>/get-example.ts
controller/<module>/get-example-rules.ts
=> validation file
Use-cases sit in "Use Cases" layer of Clean Architecture. So use-cases should only know about entity layer.
Entity should not know about / call outer layer function / class
- Create an interface for the entity. Ex:
src/database/entities/interfaces/user.ts
- Create the class that implement this interface, this is the data object class. Ex:
src/database/entities/user.ts
- Create mongoose schema model for the entity. Ex:
src/data-access/schemas/user.ts
- Register the mongoose schema model in
src/data-access/models/index.ts
. Should follow registered models - Create data-access interface. Ex:
src/data-access/interfaces/user-db.ts
- Create data-access function & factory
src/data-access/make-user-db.ts
- Instantiate data-acess function in
index.ts
file and inject dependencies (if-have). Ex:src/data-access/index.ts
Bad:
const hashedPassword = await hashPassword("plain_password_string");
Good:
const hashed_password = await hashPassword("plain_password_string");
Bad:
const hashed_password = await hash_password("plain_password_string");
Good:
const hashed_password = await hashPassword("plain_password_string");
Bad
const hasPendingRecommendation = recommendation_exists && is_pending;
Good
const has_pending_recommendation = recommendation_exists && is_pending;
Bad
await Apply_referralCode_in_background({ referral_code });
Good
await applyReferralCodeInBackground({ referral_code });
Refer to /server folder > README.md for more instructions
- cd server folder
- Get the
.env
file - Start the docker containers:
yarn dc:up
- type
yarn install
on server home directory if you have not done it before. - type
yarn dev
- Server will run on
http://localhost:3000
- If you wish to stop the docker containers,
yarn dc:down
- cd user-dashboard folder
- type
yarn install
if you have not done it before. - type
yarn dev
- access it at
http://localhost:8082
- cd admin-dashboard folder
- type
yarn install
if you have not done it before. - type
yarn dev
- access it at
http://localhost:8080
k -n staging get pod -o jsonpath='{.items[0].metadata.name}' | pbcopy
- access the DB via shell
- check db.<collection.name>.isCapped();
- db.runCommand({"convertToCapped": "logs", size: 80000000})
- run db.<collection.name>.isCapped();
Reference: https://www.geeksforgeeks.org/capped-collections-in-mongodb/
- https://www.freecodecamp.org/news/video-clean-architecture-in-node-js/
- Rules for clean code
- Node clean code architecture
- Application layer - use-cases
- Domain-driven Design articles
- Screaming architecture
- What is screaming architecture
- Clean architecture use-case structure
- Denormalize data
- *transaction in clean architecture
- Transaction in mongoose
- Concept of unit-of-work
- bob's reply to unit-of-work
- Transaction with mongoose
- How to query in mongoose with Or operator
- stackover flow on querying string array
- Example on how to use $or operator
- start_period only works from 3.4 onwards
- docker compose setup for mongoose
- Automate enabling replica set for mongodb
- https://github.com/dev-mastery/comments-api
- https://github.com/jbuget/nodejs-clean-architecture-app
- Password hashing with nodejs
- Using of bcrypt stackoverflow
- Question about auth workflow in clean architecture
- Passport multiple JWT Strategies
- Passport quicktips
- Passport how to set params into req
- Passport authentication article
- Passport authentication articles
- nodejs-security
- Passport library
- JSON Web Token library
- Passport-jwt library
- token-based authentication
- refresh and access token
- enable CORS for express
- login with username and password
- Find db for this user, and hash the password to check if the password matches.
- If match, sign the user object with accessTokenSecret, with expiry of 20m for example, and refreshToken
- Return this accessToken and refreshToken back to user
Session will then store all the sessionId in memory or database. But if you are using JWT, all your information of the user is all contained in the JWT.
You want to validate your JWT so you know the data passed in that JWT is valid for consumption.
- Mongoose Database
- Mongoose database plugin
- Mongoose to check for connected status
- How to run mongodb on docker-compose
- masterclass
- kalpha
- tigerhall => consider this UI/UX
- unibly
- ceresa
- min v14.16