visti node_modules/@types/convict/index.d.ts line 136 and add optional string value "| string" to the chain
A simple starter template for NodeJS apps using clean architecture
To learn more about clean architecture, please read this article https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
- Project Structure (adopted from Clean Architecture)
- Dependency Injection
- API Documentation using https://apidocjs.com/
- MongoDB setup
- Testing setup
- Continuos Integration Github action
- Configured Express server
- Compliance with 12 factor app
- Process manager
- Clone the repository by running
git clone [email protected]:igmrrf/Node_Clean_Architecture.git
- Create a
.env
file in the root directory. - Copy the content of env.sample into the just created .env file, and add the appropriate values.
- Install the dependencies by running
npm install
- Add data for seeding in ``src/infra/database/seeders/`. Once the app is started, it will automatically seed the database with the data you provide.
- Run the app using
npm run start
Visit http://localhost:30123/rest-docs for API documentation
- app: App contains the use cases of the system. A use case can contain business logic for accomplishig a specific goal. Similar usecases can be grouped together in directories e.g RegisterUser, VerifyUser can be grouped under "users".
⚠️ NOTE: use-cases should NEVER communicate with any external service such as a database directly. Instead, they should call an interface. - config: Environment specific configurations for the application e.g Database connection options, payment providers, etc. All files created in
config
directory are automatically loaded and exported to different application modules.
// ❌ Never ever do this!
const client = new AWS.S3({
accessKeyId: process.env.IAM_KEY,
secretAccessKey: process.env.IAM_SECRET,
Bucket: process.env.BUCKET_NAME,
endpoint: process.env.AWS_ENDPOINT,
httpOptions: {
timeout: 1000 * 60 * 5,
},
});
// ✅ Do this instead!
// All app configurations are automatically exported into this object
import config from "config";
const spacesUrl = config.get("spaces.spacesUrl");
const awsIAMKey = config.get("spaces.awsIAMKey");
const awsIAMSecret = config.get("spaces.awsIAMSecret");
const bucket = config.get("spaces.bucket");
const client = new AWS.S3({
accessKeyId: awsIAMKey,
secretAccessKey: awsIAMSecret,
Bucket: bucket,
endpoint: spacesEndpoint,
httpOptions: {
timeout: 1000 * 60 * 5,
},
});
-
domain: Domain contain domain specific code e.g entities. A domain here is the real-world context in which you're attempting to solve a problem using software i.e the business thee app will be used for. An entity can be an object with methods, or it can be a set of data structures and functions that mirror their real life counterparts. Example of entities in the e-commerce domain might be Customer, Product, Cart, etc.
⚠️ NOTE: entities should NEVER communicate with any external service such as a database directly. -
helpers: Helper functions
-
infra: All external entities in your app go in here. For example your database, cache, logger, object storage, messaging, payment gateway.
- repositories: Repositories implement logic for retrieving and aggregating data from databses. All repositories inherit common methodds from the
BaseRepository
class. So you don't have to keep writing similar queries multiple times.
- repositories: Repositories implement logic for retrieving and aggregating data from databses. All repositories inherit common methodds from the
-
interfaces: Interfaces are delivery mechanisms for your app i.e how users access your app. For example, through a REST API, gRPC server, GraphQL. In this example, we are delivering our application using Express web framework for Nodejs.
-
scripts: One time scripts go here. Read https://12factor.net/admin-processes to learn more
Dependency injection has been setup by default. Read https://stackify.com/dependency-injection/ to learn more about DI (Dependency Injection). awilix is the package used for implementing DI. Read this article to get familiar with Awilix. All models, use cases, entities you create are loaded automatically into the container.
// NEVER do this! ❌
import Mystuff from "../here/there/onemore/last/mystuff.js"; // 🤮
// Do this instead! ✅
import MyStuff from "alias/mystuff.js";
Create aliases to make your imports cleaner. To add new aliases, modify .babelrc.js
and jsconfig.json
.
To generate documentation for API endpoints, we are using https://apidocjs.com/. You can use Postman or whatever, but apidocjs generates your API documentation from annotations you add to your code.
Never run your app directly against node. Use a process manager. This template uses PM2
npm run build
- Compiles src code into distnpm run start
- Starts the app in production modenpm run start:dev
- Starts the app in development modenpm run test
: Run testsnpm run lint
: Lint code using ESLintnpm run db:seed
- Seeds the databasenpm run docs:api
- generates API docs
I was trying to make this as lean as possible, but if you think there's something I should add, please send in a PR. Thank you!
igmrrf A litmus test for whether an app has all config correctly factored out of the code is whether the codebase could be made open source at any moment, without compromising any credentials.
https://github.com/fluent/fluentd
{
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "airbnb-base", "prettier"], "plugins": ["@typescript-eslint"], "parser": "@typescript-eslint/parser", "parserOptions": { "project": true, "tsconfigRootDir": "__dirname" }, "ignorePatterns": ["src/scripts/gen/*"],
"root": true, "rules": { "quotes": [2, "double", { "avoidEscape": true }], "max-len": ["warn", { "code": 14 }], "no-underscore-dangle": [ "error", { "allow": ["_id"] } ], "import/prefer-default-export": "off", "class-methods-use-this": "off", "import/no-dynamic-require": "off" }, "settings": { "import/resolver": { "babel-module": {}, "node": { "paths": ["src"], "extensions": [".js", ".jsx", ".ts", ".tsx"] } } } }