isa-synct v0.0.1
isa-syncT
Initial template for your services!.
Comes with:
- TypeScript;
GenericException
base for all exceptions;BaseController
for controllers extends from it;- Dockerfile;
- RabbitMQ consumers/producers;
- Swagger endpoints using
swagger-express-ts
package; - TSDoc with
typedoc
package; - Kubernetes deployment config;
How to run
- Run
yarn dev
to spawn a nodemon server watching source files - Create a .env file in root. This file keep some important enviromment variables has:
- PORT
- MONGO_URL
- MONGO_DB
Edit src/config/env.ts
with your current needs
Common everyday commands
yarn dev
: starts a nodemon serveryarn build
: builds the projectyarn build:webpack
(requiresyarn build
): build the project into a single file with webpackyarn build:docker
: builds a docker image of this serviceyarn publish:docker
: (requiresyarn build:docker
) push the docker imageyarn format
: uses Prettier to format your codeyarn lint --fix
: runs tslintyarn build:docs
: runstypedoc
and generate documentationyarn release
: creates a new tag and changelog with a given release number
Usage
Controllers
Controllers of this boilerplate are handled by inversify-express-utils
package.
Here is a exemple:
@controller('/sync')
export default class SyncController {
@inject(REFERENCES.SyncService) private syncService: SyncService;
@ApiOperationGet({
description: '',
summary: '',
responses: {
200: {
description: 'Success',
type: SwaggerDefinitionConstant.Response.Type.ARRAY,
model: '',
},
},
})
@httpGet('/')
@withException
async getSyncs(@response() res: Response) {
const result = await this.syncService.find({});
res.status(OK).send(result);
}
@httpPost('/', ..._createEditSyncValidator)
@withException
async postSync(@request() req: Request, @response() res: Response, @requestBody() sync: SyncInterface) {
this.syncService.validateRequest(req);
const newSync = await this.syncService.insert(sync);
res.status(CREATED).send(newSync);
}
Everything is injected by inversify
and the composition root lives in src/config/inversify.config.ts
.
Inside the composition root, we import all controllers and inversifyjs
takes care to setup our application (as seen on src/index.ts
)
To use swagger, see documentation of swagger-express-ts
(https://github.com/olivierlsc/swagger-express-ts)
Services
The service layer extends the BaseController<T>
which has all methods to handle the mongoose model (with has typings too!).
RabbitMQ
To use a consume/producer function for RabbitMQ, bootstrap the connection on your service
like this:
/**
* Creates a RabbitMQ Channel and setup the queue for this service
*/
// Run this function on constructor
private async _createRabbitMQChannelAndSetupQueue() {
this._channel = await createRabbitMQChannel(env.rabbitmq_url);
// Some consumer on ./src/queue/consumers
consumeCreateUser(this._channel, this._consumeCreateUser);
}
/**
* RabbitMQ Consumer CREATE_USER Function
*
* Creates a user
* @param payload RabbitMQ ConsumeMessage type.
*/
private _consumeCreateUser = async (payload: ConsumeMessage) => {
const data = JSON.parse(payload.content.toString());
/** DO SOMETHING */
this._channel.ack(payload); // sends a acknowledgement
};
The producer is straight forward: just call the function that sends something to a queue (ex: ./src/queue/producers/
)
Exceptions
All exceptions that are catch by src/shared/server/middlewares/exception.ts
, have GenericException
as they base (some edge cases will be handled by the default express
error handler).
Currently built-in exceptions:
AuthException
: should be throw on authentication errorsEntityNotFoundException
: should be throw on not found queriesMongoNotConnectedException
: internal exception for mongo errorsRouteNotFoundException
: throw when you hit a route that doenst't existUnprocessableEntityException
: should be throw on a entity can't be saved/updatedUpstreamConnectionException
: internal exception forRemoteController
exceptions
Service authorization
In src/shared/server/middlewares
you can find a Unauthorized.ts
file that handles authorization logic of this service.
Using this middleware, you should have another service with endpoint /auth
that receives a JWToken
via Authorization
header.
If that service responds with 200, you're authorized to procced with your request into this service.
Dependency Injection
This template uses inversifyjs
to handle DI with a IoC container.
The file that handles that is src/config/inversify.config.ts
import '../entities/Tenant/TenantController';
import '../shared/middlewares/HealthCheck';
import { Container } from 'inversify';
import REFERENCES from './inversify.references';
import Connection from '../shared/class/Connection';
import TenantService from '../entities/Tenant/TenantService';
import tenantModel from '../entities/Tenant/TenantModel';
import RemoteController from '../shared/class/RemoteController';
const injectionContainer = new Container({ defaultScope: 'Singleton' });
injectionContainer.bind(REFERENCES.Connection).to(Connection);
injectionContainer.bind(REFERENCES.RemoteController).to(RemoteController);
injectionContainer.bind(REFERENCES.TenantService).to(TenantService);
injectionContainer.bind(REFERENCES.TenantModel).toConstantValue(tenantModel);
export default injectionContainer;
If your controller has another class dependency, inject the dependency onto your class like this:
export default class SyncController {
@inject(REFERENCES.SyncService) private syncService: SyncService;
}
Docker and Kubernetes
To build a docker image, you have to build the project using npm run build
and npm run build:webpack
. Then, use npm run build:docker
, and to publish, use npm run publish:docker
. Remember to edit these commands if you use private repos.
The Kubernetes deployment file (deployment.yaml
), has a LivenessProbe
that checks if the route /health
returns 200. This route, pings to the database. If something goes wrong, your service will be restarted.
The deployment also has a TimeZone
configuration that you can set the TimeZone
of the running container. The default is pointing to America/Recife and you can change this anytime if you need.
The Service
object in deployment.yaml
file expose the Pod
created by the Deployment
to the world on port 80 and binding the port 3000 of the Pod
to it.
After configuring, you need to add the Service
definition in a ingress
controller of your k8s cluster.
Since this template uses Kubernetes, the .dockerignore
and Dockerfile
files DOESN'T have a reference to .env
file (which, also is ignored on .gitignore
file). The way we use here on @e3labs is setting a envFrom
field on deployment.yaml
.
Here is a example:
apiVersion: apps/v1
kind: Deployment
metadata:
name: e3-product-service
spec:
replicas: 4
selector:
matchLabels:
app: e3-product-service
template:
metadata:
labels:
app: e3-product-service
spec:
containers:
- name: e3-product-service
image: <some-image>
ports:
- containerPort: 3000
envFrom:
- configMapRef:
name: env-config
livenessProbe:
initialDelaySeconds: 20
periodSeconds: 5
httpGet:
path: /health
port: 3000
Always remember:
- Set configmap on your cluster
- Change the image of your container to match your registry
4 years ago