A starter kit for Node.js project written with TypeScript.
This project is well organized, scalable and maintainable boilerplate as your application's business grows.
It's is recommended before start to have a basic knowledge about the following
- TypeScript.
- PostgreSQL.
- Sequelize ORM.
- Express.
- Express Validator.
- Json API with examples (optional).
- Problem definition.
- How to solve the problem.
- Getting Started
- Project structure.
- Database (PostgreSQL Integration).
- API
- Static code analysis.
As we know JavaScript doesn't enforce type checking by itself, this may not be a problem when developing small Node.js apps, but it will be a BIG problem when building a multi-module or scaled apps.
Since TypeScript is a super set of JavaScript, we can have Node.js apps to be written using TyeScript.
TypeScript is transpiled using TypeScript Compiler - also known as tsc - which can be adjusted to output the desired version of ECMAScript.
-  If you have yarn installed on your machine: Open your terminal on the project's root folder & run the following command yarn 
-  If you don't have yarn installed on your machine: Open your terminal on the project's root folder & run the following command npm i -g yarn then yarn 
yarn run devyarn run buildyarn run startWe are building a simple electronics store with to basic entities
- Category.
- Product.
Each Category will have the properties
- id: number(🔑 primary key).
- name: string.
Each Product will have the properties
- id: number(🔑 primary key).
- name: string.
- price: number.
- categoryId: number(🗝 foreign key from category).
./src └── app ├── app.ts ├── categories │ ├── data │ ├── models │ └── validation ├── controllers ├── products │ ├── data │ ├── models │ └── validation ├── shared │ ├── db │ │ ├── models │ │ ├── helpers │ ├── middleware │ ├── models │ └── utils └── startup └── server.ts-  app.ts is the entry point of the application. 
-  startup/server.ts file is where Node.js server options getting set. 
-  Each folder on the app folder represents an application module. 
-  shared module contains database definition, utilities and shared models which will be used by other application modules. 
-  controllers module is where all application modules' routes are being defined. 
-  Each module may contains - data folder to define its data-access functionalities.
- models folder in which the module models are defined.
- validation folder in which all models validations are defined, e.g. validating a model for creating a category.
- Each folder must has a index.ts by a convention to export each (class, interface, function & etc..) defined in this folder.
 
Until now this boilerplate supports five types of databases:
- In-memory Database (branch master).
- PostgreSQL Database (current branch).
- MySQL Database (branch mysql-integration).
- MSSQL Database (branch mssql-integration).
- MongoDB Database (branch mongodb-integration).
We are using a postgresql database along with sequelize ORM.
The database schema and definition could be found in the location ./src/app/shared/db .
Each database table must be defined as a sequelize model under the folder ./src/app/shared/db/models .
In the file ./src/app/shared/db/models/index.ts after exporting all defined models we should setup all models relations.
We use a class called Database defined in the file ./src/app/shared/db/helpers/database.model.ts as central point to deal with all database models.
import { Sequelize } from 'sequelize'; import { Category, Product } from '../models'; /*   Connect to database.  NOTE: i'm connecting here to the database `typescript_in_nodejs_starter_db` on my `localhost` with username `postgres` and password 'fawzy'.  You can change this data and use your data instead.  */ /**  * A singleton instance of sequelize that will be used across the application.  *  * @summary It's important to not use any other instances of sequelize other than this instance unless you have more than one database.  */ const sequelize = new Sequelize('typescript_in_nodejs_starter_db', 'postgres', 'fawzy', { host: 'localhost', dialect: 'postgres', logging: false /* Stop logging sql queries unless your are tracing some problems. */ }); /**  * The Database helper that includes all functionalities related to the database and all of it's models.  * @summary All of this class members are static.  * @summary All database models should be registered in this class.  * @summary All database models should be accessed only through this class as central point of database functionality.  */ export class Database { /**  * A singleton instance of sequelize that will be used across the application.  *  * @summary It's important to not use any other instances of sequelize other than this instance unless you have more than one database.  */ public static readonly sequelize: Sequelize = sequelize; /**  * Tests the connection to the database using the provided credentials.  */ public static testDatabaseConnection(): Promise<void> { return sequelize.authenticate(); } /**  * Sync all defined models to the DB.  * @param force If force is true, each DAO will do DROP TABLE IF EXISTS ..., before it tries to create its own table  */ public static syncDatabase(force?: boolean): Promise<never> { return sequelize.sync({ force: force }); } /**  * The Category model that maps the `categories table` in the database.  * The model name will be `categories` also.  */ public static get Categories(): typeof Category { return Category; } /**  * The Product model that maps the `products table` in the database.  * The model name will be `products` also.  */ public static get Products(): typeof Product { return Product; } }You can find the database scripts under the ./resources folder
- typescript_in_nodejs_starter_db.sql (a full database schema & data script).
- typescript_in_nodejs_starter_db_data.sql (a data only script).
Since we follow the Json API standards, our web api should always return a response with this structure
/**  * Represents an application http-response's body in case of success or even failure.  */ export interface AppHttpResponse { /**  * Gets or sets the data that requested or created by the user.  *  * This property will has a value only if the request was succeeded.  */ data?: unknown; /**  * Gets or sets the metadata for the http response.  */ meta?: AppHttpResponseMeta; /**  * Gets or sets a set of errors that occurs during request processing.  *  * @summary e.g. validation errors, security errors or even internal server errors.  */ errors?: AppHttpResponseError[]; }Where the meta has the structure
/**  * Represents an application http-response metadata.  */ export interface AppHttpResponseMeta { /**  * Gets or sets the current pagination page.  */ page?: number; /**  * Gets or sets the maximum allowed items per-page.  */ pageSize?: number; /**  * Gets or sets the count of the actual items in the current page.  */ count?: number; /**  * Gets or sets the total count of items available in the database those match the query criteria.  */ total?: number; /**  * Gets or sets the number of the previous pagination page.  */ previousPage?: number | undefined; /**  * Gets or sets the number of the next pagination page.  */ nextPage?: number | undefined; /**  * Gets or sets the total available pagination pages those match the query criteria.  */ totalPages?: number; }And each error in the errors property has to be
/**  * Represents an app http error that should be sent within a failed request's response.  *  * @summary All error members are optional but the more details the server sends back to the client the more easy it becomes to fix the error.  */ export interface AppHttpResponseError { /**  * Gets or sets the application-specific code for this error.  */ code?: AppErrorCode; /**  * Gets or sets the name of the source that causes this error.  *  * Usually it's the name of the property that causes the error.  *  * The property maybe a nested property,  * in this case use e.g. if we are validating a `Person` object use `address.postalCode` instead of `postalCode`.  */ source?: string; /**  * Gets or sets a generic title of the problem.  */ title?: string; /**  * Gets or sets a more descriptive details for the problem, unlike the generic @field title.  */ detail?: string; }The code member of an error object contains an application-specific code representing the type of problem encountered. code is similar to title in that both identify a general type of problem (unlike detail, which is specific to the particular instance of the problem), but dealing with code is easier programmatically, because the “same” title may appear in different forms due to localization as described here.
Our application-specific codes are defined in an Enum called AppErrorCode with the following definition
/**  * The application-specific error codes.  */ export enum AppErrorCode { /** Un-authenticated code. */ UnAuthenticated = 1, /** Access denied or forbidden code. */ Forbidden = 2, /** Internal server code. */ InternalServerError = 3, /** The field is required code. */ IsRequired = 4, /** The field type is invalid. */ InvalidType = 5, /** The field type is String and its length is invalid. */ InvalidLength = 6, /** The entity field value already exists in another entity. */ ValueExists = 7, /** The entity can't be deleted due to its existing relations with other entities. */ CantBeDeleted = 8, /**  * The related entity isn't found,  * @summary e.g. you are trying to create a new product in a category which is not exists in the database.  */ RelatedEntityNotFound = 9 }-  A success response Searching for a list of products those matching some criteria [GET]http://localhost:3000/api/products?name=Samsung&categories=1,2,3&page=1&pageSize=3Should return a response like the following { "data": [ { "id": 1, "name": "Samsung Galaxy S5", "price": 5000, "categoryId": 1, "category": { "id": 1, "name": "Mobiles" } }, { "id": 2, "name": "Samsung Galaxy S6", "price": 4500, "categoryId": 1, "category": { "id": 1, "name": "Mobiles" } }, { "id": 15, "name": "Samsung 32 Inch HD LED Standard TV - UA32K4000", "price": 3550, "categoryId": 3, "category": { "id": 3, "name": "TVs" } } ], "meta": { "page": 1, "pageSize": 3, "count": 3, "total": 3, "totalPages": 1 } }
-  A bad-request response Try to create a new product with a name that is already exists in the database [POST]http://localhost:3000/api/products{ "name": "Samsung Galaxy S5", "price": 12300.0, "categoryId": 1 }Should return a response like the following { "errors": [ { "code": 7, "source": "name", "title": "Field value already exists", "detail": "Product name already exists" } ] }
-  The internal server error, un-authenticated and forbidden responses should follow the same convention. 
We are using the following plugins to statically analysis our code