This is a real-time API backend project, written in TypeScript using NodeJS, Nest, ExpressJS and other libraries.
It includes/has features of:
- Real-time API - The project uses Socket.IO and RxJS for its reactive and real-time nature.
- Database - MongoDB is used as the DB and Mongoose as the ORM.
- Deployment - The production versions could be deployed to AWS Elastic Beanstalk, using PM2 for clustering and fault tolerance.
- Payments Accepting - via Stripe API.
- TypeScript - Main Programming Language.
- NodeJS - Run-time environment.
- NestJS - A progressive Node.js framework for building efficient, reliable and scalable server-side applications.
- MongoDB - Database.
- AWS Elastic Beanstalk - Hosting (optionally).
- Stripe API - Payments accepting.
- ExpressJS - Web Framework
- Socket.IO - For Real-time API
- RxJS - For Reactive/Event Based Programming
- InversifyJS - Inversion Control (Dependency Injection, used in addition to NestJS DI)
- Mongoose - ORM
- PM2 - NodeJS Production process manager (optionally, used in production environments)
- Install the dependeinces by running:
$ yarn install
- Take a look for
.env.template
file, create.env
file with the same variables and fill them appropriately.
Simply run:
$ yarn start
Note: make sure you have MongoDB installed locally with default settings
To run tests execute following command:
$ yarn run test
To be added
/.ebextensions - folder with AWS Elastic Beanstalk configuration files.
/.elasticbeanstalk - folder AWS Elastic Beanstalk configuration files.
/build - TypeScript transpiler build directory.
/certificates - SSL Certificates (optionally)
/dist - WebPack build output directory
/docker - Docker configuration (currently not in use)
/node_modules - folder with NPM packages (created after `yarn install`)
/res - Resourses, such as pages that sent to the clients in text form or json.
/src - All the code of the project
|-->/app.ts - The main application file.
|-->/inversify.config.ts - The Dependency Injection config.
|-->/pm2bootstrap.ts - The file AWS runs to start the program.
|
|-->/main - The main application file
|-->/modules - contains the shared modules used in this project.
|-->/pyro - Custom written library for socket io and mongoose wrapping with classes.
\-->/test - Testing code
/tmp/logs - All the logs created during server run locally or in production
/.env - The configuration file. (which developer should create from .env.template)
/.env.template - The configuration file template.
/package.json - NPM packages config file.
/tsconfig.json - TypeScript compiler config file.
Pyro DB is a wrapper for mongoose written in TypeScript which allows declaring models like that:
@ModelName('User')
class User extends DBObject<IUser, IUserCreateObject> implements IUser {
@Schema({ type: String, required: false })
public firstName?: string;
@Schema({ type: String, required: false })
public lastName?: string;
@Schema({ type: String, required: false })
public email?: string;
@Schema(getSchema(GeoLocation)) public geoLocation: GeoLocation;
@Types.String() public apartment: string;
@Schema({ type: String, required: false })
public stripeCustomerId?: string;
@Schema([String]) public devicesIds: string[];
}
-
The
@ModelName
decorator signals that a class is a model. -
@Schema
specifies the schema of some field,getSchema
is used to use embed other model. -
@Types.String(default)
,@Types.Number(default)
,@Types.Boolean(default)
and@Types.Date(default)
are used for primitive schemas. All of them are not nullable by default. -
To make a reference to some model in another collection.
@Types.Ref
is used like this:@Types.Ref(Carrier, { required: false })
It also contains the DBService
abstract base class, which contains basic operations for every model with collection in the database. It follows the following structure:
export interface IDBService<CreateObject, DBObject extends CreateObject> {
create(createObject: CreateObject): Promise<DBObject>;
remove(objectId: string): Promise<void>;
removeAll(): Promise<void>;
get(objectId: string): Observable<DBObject | null>;
getMultiple(ids: string[]): Observable<DBObject[]>;
find(conditions): Promise<DBObject[]>;
findOne(conditions): Promise<DBObject | null>;
update(objectId: string, updateObj: any): Promise<DBObject>;
updateMultiple(findObj: any, updateObj: any): Promise<DBObject[]>;
updateMultipleByIds(ids: string[], updateObj: any): Promise<DBObject[]>;
}
Many of the services in this projects extend DBService
, for example:
class WarehousesService extends DBService<IWarehouseCreateObject, IWarehouse, Warehouse> implements IWarehouseRouter, IService {
public readonly DBObject = Warehouse;
...
@observableListener()
get(id: string) {
return super.get(id);
}
}
To make any method of the DBService
publicly available as part of the API please use the @<some>Listener
from Pyro IO.
Allows declaring routers with auto generated Socket.io API right inside existed Services. That makes it possible to execute corresponding Service methods from incoming WebSocket messages and reply back without the need to create additional Controllers on top of Services
@routerName('users')
class UsersService {
// returns Observable!
@observableListener()
get(id: string): Observable<User> {
// ...
}
// returns Promise!
@asyncListener()
async register(user: IUserCreateObject): Promise<User> {
// ...
}
}
@routerName
- marks the class as a router. That makes it possible to call Service methods via WebSockets connection.@observableListener()
is used to mark methods that return observable which allows real-time data-transfer to the client.@asyncListener()
is used to mark methods that return promise, which allows returning one time response to the client.