This package wraps hydra express and it's all built in typescript it offers hydra and hydra express interfaces , controllers , middlewares and dependency injection capabilities
First install it by this command
npm i hydra-promoted
also to be able to use dependency injection you will need to install inversify
npm i inversify
After that please install this library for decorators dependency injection
npm i reflect-metadata
then don't forget to import it in your app.ts (assuming you are in a typescript project).
import 'reflect-metadata';
Now make sure in your tsconfig.json you have enabled the decorators , emitted their metadata and added reflect-metadata to the types field
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"types": ["reflect-metadata"],
using hydra-promoted allows you to have controllers in express
so , for example if your code is inside directory called src
create a folder called controllers
and another folder called middlewares
inside it.
then assuming you are in a typescript project add those lines to your app.ts
In this version we have renewed how you can initialize the controllers and middlewares to prevent dynamic imports since it makes some problems if you are using something like webpack for example
so here is the new way to initialize the controllers and middlewares
import { Controllers , Middlewares } from 'hydra-promoted';
import { AuthMiddleware } from './middlewares';
import { ExampleController } from './controllers';
// use it like that
Controllers.provide([
ExampleController
or
{ instance: ExampleController , name: 'TestController' }
]);
Middlewares.provide([
AuthMiddleware
or
{ instance: AuthMiddleware , name: 'TestMiddleware' }
]);
the above lines registers your controllers and middlewares that are in those arrays so , they can be used later using the Routers.
IMPORTANT NOTE When putting the controller instance like that this will be it's name to be used in making the routers to them .
and note here this is another way to change the instance name if there are two middlewares with the same name you can use this way to change a middlewares name. by specifying the instance and the instance name to be used with in the Routers
{ instance: AuthMiddleware , name: 'TestMiddleware' }
also it's usually recommended to put the above controller and middleware providers
in separate config files for example ControllerRegistery.ts
and MiddlewaresRegistery.ts
NOTE do not forget to register the controller in the controllers provider as explained above
A Controller should look like that for the provider to be able to use it.
import { injectable } from 'inversify';
import { AppRequest, AppResponse } from 'hydra-promoted';
@injectable()
export class ExampleController {
index(req: AppRequest, res: AppResponse) {
// your code here
}
}
now how will you route to that controller open the file where your routes exist for example the index.ts file
this is how to use that controller
import { Router } from 'hydra-promoted';
// this will register the '/' to go to the example controller and run // the index function.
Router.get('/', 'ExampleController@index');
module.exports = Router.getRouter();
middlewares should look like that
import { injectable } from 'inversify';
import { AppRequest, AppResponse } from 'hydra-promoted';
@injectable()
export class TestMiddleware {
handle(req: AppRequest, res: AppResponse, next: Function) {
console.log('Hello world Middleware');
return next();
}
}
NOTE don't forget to register your middleware using the middleware provider mentioned in the first section.
middlewares are used like that
import { Router } from 'hydra-promoted';
// this will register the '/' to go to the example controller and run // the index function.
Router.get('/', 'TestMiddleware','ExampleController@index');
module.exports = Router.getRouter();
As you have noticed above there were some decorators above the controllers those helps the dependency injection DI inside hydra-promoted to make instances from those controllers and middlewares.
we will now see how to make services and inject them. you should also know that we are using inversify js to make all this possible.
Also you may refer to inversify docs if you have any difficulties understanding.
First we will start making an example service
import { injectable } from 'inversify';
@injectable()
export class ExampleService {
testMe() {
console.log('Hello world is an Example service');
}
}
now you should register the services in the bindings array
create a file called DIManager.ts
which will look like that
import { DIManager } from 'hydra-promoted';
import { ExampleService } from '../serviceProviders/ExampleService';
DIManager.registerServices([ExampleService]);
as you can notice we have imported the DIManager from the hydra-promoted which will put that service in the bindings container for inversify js.
now you want to inject and use your service this is how you can do it in your controller also
NOTE that any class with the @injectable
can inject any service as long as it is registered in the DI container as in the above snippet.
import { injectable } from 'inversify';
import { AppRequest, AppResponse } from 'hydra-promoted';
import { ExampleService } from '../serviceProviders/ExampleService';
@injectable()
export class ExampleController {
// NOTICE here how we injected our service
constructor(public testService: ExampleService) {}
index(req: AppRequest, res: AppResponse) {
this.testService.testMe();
return res.sendOk({
hello: 'world'
});
}
}
as easy as that your service instance exists with you in the controller also a service can also be injected in another service.
This section will explain how to make api requests to other hydra services since hydra provide load balancing if we have multiple services of the same service.
we have two methods for making api requests
This method is the normal way of requesting we just give it a UMF message and then it will send the request
you can check the UMF message format docs here.
import { HydraApiRequest } from 'hydra-promoted';
let result = await HydraApiRequest({
body: { message: 'some data'},
to:'serviceName:[method]/route/path'
});
and the response will just be a json object that looks like that
{
headers: { // response headers here},
result: { // response body },
statusCode: 200,
statusDescription: '',
statusMessage: 'Ok'
}
NOTE: this is the response standard shape so , you will always receive something like the above json in any response from a hydra service
This is the second method which provides secure encrypted requests between the hydra services using the Rsa encryption.
it's usage is exactly similar to the above method but with additional required field which is the public key of the service you are going to call.
let result = await HydraSecureApiRequest({
body: { message : 'some data' },
to: 'serviceName:[method]/path/to/router',
publicKey: './public.pem'
});
as you can see it is the same with the additional field the public key
responses from the HydraSecureApiRequest will be exactly the same as the above method as it will handle any decryption needed to be done on the responses.
Now you should be asking what if I am the receiver of the request should I handle everything myself in that case we are exposing a middleware which handles all the decryption logic for you as a receiver for the Rsa encrypted request
middleware is called HandleRsaRequest
this is a normal express middleware which can be used like that
import { HandleRsaRequest } from 'hydra-promoted';
app.use(HandleRsaRequest('./private.pem'));
as you can see it just requires the private key path or object which will handle all the decryption of the request needed for you and you will then receive a normal request as usual.
Sometimes the private key will have a passphrase so , in such a case the private key property will accept an object instead of a string
import { HandleRsaRequest } from 'hydra-promoted';
app.use(HandleRsaRequest({
path: './private.pem',
passphrase: 'supersecretpassphrase'
}));
also this is how you can use in the middleware that we provide
import { HandleRsaRequest, Middleware, AppRequest, AppResponse } from 'hydra-promoted';
import { injectable } from 'inversify';
@injectable()
export class HandleRsa implements Middleware {
handle(req: AppRequest, res: AppResponse, next: Function) {
return HandleRsaRequest('./private.pem')(req, res, next);
}
}
we are just calling the HandleRsaRequest which will return a middleware and we are just passing it the req , res and next that it needs it.
the sender will be waiting for encrypted response too from the receiver so, we need to have a method too to encrypt the receiver response.
import { injectable } from 'inversify';
import { AppRequest, AppResponse, SendSecure } from 'hydra-promoted';
@injectable()
class ExampleController{
securedFunction(req: AppRequest, res: AppResponse) {
return SendSecure({
body: "This response string will be encrypted",
privateKey:{
path: './privateKey.pem',
passphrase: '123'
},
res: res
});
}
}
as the example above this SendSecure
function will handle all the encryption logic for you
it just encrypts the response using the private key and the other requesting service will just use
the receiving side public key to decrypt it.
hydra promoted also provide some helpers for encryption and decryption which ease out the Rsa encryption and decrpytion steps.
import { RsaEncryptWithPrivate } from 'hydra-promoted';
let encryptedString = await RsaEncryptWithPrivate("string you want to decrypt","./privatekey/path.pem");
or
let encryptedString = await RsaEncryptWithPrivate("string you want to decrypt",{
path: "./privatekey/path.pem",
passphrase: process.env.PASS_PHRASE
})
the return result is the encrypted is a promise which resolves to your encrpyted string in a base64 string format
import { RsaDecryptWithPublic } from 'hydra-promoted';
let yourString = await RsaDecryptWithPublic(encryptedString,"./publickey/path.pem");
the rsa decrypt just need your encrypted string and the public key path to decrypt
NOTE of course the the public key should be the other key pair for the encryption private key.
Now if you were wondering where is the hydra part in this there you go hydra-promoted exposes the following variables which will let you use Hydra and hydra express with intellisense.
import { HydraExpress } from 'hydra-promoted';
This is the hydra express instance .
import { Hydra } from 'hydra-promoted';
This is the Hydra instance itself.
import { ServerResponse } from 'hydra-promoted';
This is a hydra utility for adding service responses.
import { ExpressInstance as Express } from 'hydra-promoted';
This is the express instance which is used inside hydra.
added feature that you can now not only send string handlers that target controllers now you can also put functions instead of strings exactly as express
Example
Router.get('/path',(req,res) => res.send('I can work with that'));
bug fix where the middleware handle functions were losing context of the middleware class
bug fix where the controller function error handler catch was not running in the case of a promise function controller now all controller functions are automatically handled
- bug fix where HydraApiRequest was not working correctly from the controllers
- adding a new function for sending an encrypted response back from an encrypted request
- hydra does not accept string as body but this is handled inside the hydraMakeRequest function by including that string inside an object with a
message
as key and the string as the value. - fixed a bug where encryped and decrypted body were not parsed and they stayed on their string forms
exposed the Rsa encrypt private and decrypt using public functions