gateway-service v0.5.0
GatewayService
A set of Typescript decorators to easily build clients to connect to remote services over HTTP and WebSocket.
Quick start
- Install gateway-service NPM package
npm install gateway-service
- Create a class, annotate it with
@GatewayService
and set the base url to your HTTP remote service
@GatewayService("http://localhost:8080")
class MyService {
}
- Add a method to your class, annotate it with
@Get
and provide a relative path to a remote resource
@GatewayService("http://localhost:8080")
class MyService {
@Get("/teams")
getTeams(): Promise<Array<Team>> { return null; }
}
- And that's it! Now you can access your remote resource by simply creating an instance of your service and invoking your method
const service: MyService = new MyService();
service.getTeams()
.then((teams: Array<Teams>) => {
// ... here you are your teams
}).catch((error) => {
// ... handle error
});
Reference guide
HHTP services
@GatewayService
decorator
In order to use a class as a gateway service you must annotate it with @GatewayService
decorator. This annotation is also useful to provide the base url to your remote service.
@GatewayService("http://localhost:8080")
class MyService {
}
Once you have your class annotated with @GatewayService
, you can get its methods implemented 'on the fly' by decorating them with one of the provided annotations (see below).
- HTTP method decorators:
@Get
,@Post
,@Patch
,@Put
and@Delete
gateway-service provides five annotations (@Get
, @Post
, @Patch
, @Put
and @Delete
) to access your remote service using HTTP requests. These decorators are the counterpart of its corresponding HTTP verbs. Simply annotate your method with one of them and you are ready to go...
@Get("/teams")
getAllTeams(): Promise<Array<Team>> { return null; }
@Post("/teams")
addTeam(team): Promise<Team> { return null; }
@Delete("/teams/1")
deleteTeam(): Promise<any> { return null; }
... and so on.
Note: If we assign a type for the method output, such as
Promise<Array<Team>>
in the first example, we must return something (for example, anull
value) to avoid compilation errors. Don't worry about this return statement, as it will never be executed.Note: When you annotate a method with one of these decorators, the output is always a
Promise
.
- Path parameters:
@Path
Commonly, when accessing a resource we need to set an "id" as part of the url (for example, GET /teams/1
) and this "id" is provided at run-time. To handle this situation you must use @Path
decorator to annotate a method parameter. The value of this parameter will be added to the resource url.
The @Path
decorator accepts an argument that is used to indicate the place in resource url where the value must be placed.
For example, given the following method declaration:
@Get("/teams/${id}")
getTeamById(@Path("id") myId) { }
then if we execute getTeamById(25);
the request will be: GET /teams/25
- Query parameters:
@QueryParameters
If we annotate a method parameter with @QueryParameters
decorator, the attributes of this parameter will be used to build a query string which will be added to the resulting URL.
For example, given the following method declaration:
@Get('/players')
getByQuery(@QueryParameters params: Object) { }
then if we execute getByQuery({ name: 'leo', surname: 'messi' });
the request will be: GET /players?name=leo&surname=messi
- Adding a body:
@Body
To supply an object as the body of the http request we use @Body
decorator. The method parameter annotated with @Body
will be copied in the body of the request at run-time.
For example, given the following method declaration:
@Post('/players')
addPlayer(@Body player) { }
then if we execute addPlayer({ name: 'leo', surname: 'messi' });
the request will be: POST /players
and the request body will be: { name: 'leo', surname: 'messi' }
- Mapping server response:
@Mapping
Sometimes, the server response is not "flat", but built based on embedded collections or wrapper objects. This makes it difficult for clients to map responses to model objects. @Mapping
decorator allows us to flatten complex server responses to easily map them to model objects.
@Mapping
accepts an argument which is a function that receives the server response as a parameter.
For example, suppose that after a GET /players
request, you obtain this response:
{
result: {
players: [ { ... }, { ... }, ... ]
}
}
then we could map that response with this method definition:
@Get("/players")
@Mapping(httpResponse => httpResponse.result.players)
getAllPlayers(): Promise<Array<Player>> { return null; }
- Handling errors
When an error is thrown during the execution of a gateway-service method, a GatewayServiceError
object is created with the following properties and types:
error: string;
message: string;
cause: string;
statusCode: number;
path: string;
stackTrace: Array<string>;
timeStamp: number;
Depending on the error type and its source (client/server) some of the properties will be filled in or left empty. Also, some of the error properties are specially focused on (but not limited to) catching errors coming from Java servers: for example error
, message
and cause
will be filled in when a Java exception is thrown from server side.
GatewayServiceError
class provides "getter" methods to access to error properties. Here is an example of error handling:
service.getTeams()
.then((teams: Array<Teams>) => {
// ... here you are your teams
}).catch((error: GatewayServiceError) => {
console.log(`
Your request could not be executed.
Status code: ${error.getStatusCode()}
Message: ${error.getMessage()}
`);
});
WebSocket services
- WebSocket support
You can also connect to a STOMP broker over WebSocket with gateway-service. Annotate your class with @GatewayServiceWebSocket
and you will be able to send and receive messages. As STOMP is used, messages must be of type string
(you can use JSON.stringify/JSON.parse to deal with objects).
- Setting up a WebSocket connection
You can set up a WebSocket connection by using the class level @GatewayServiceWebSocket
decorator. This annotation accepts a mandatory parameter (the WebSocket server URI).
@GatewayServiceWebSocket("ws://localhost:8000")
class MyWebSocketService {
}
This decorator also accepts a second optional parameter (a boolean value) that allow you to activate debug. If you need to pass extra parameters as well (like login and password) you can use an optional third parameter, which is an object, that will be included in the connection call.
In the next example we activate debug and pass headers with credentials information.
@GatewayServiceWebSocket("ws://localhost:8000", true, { "login": "fakelogin", "passcode": "fakepass" })
class MyWebSocketService {
}
Once you have your class annotated with @GatewayServiceWebSocket
, you can get its methods implemented 'on the fly' by decorating them with one of the provided annotations (see below).
- Sending messages over WebSocket
To send a message, annotate your method with @SendMessage
and add a destination as parameter. Then declare a parameter annotated with @MessageBody
and that's it. Remember that gateway-service use STOMP over WebSocket, so messages must be strings.
Example:
@SendMessage("/soccer/news")
sendMessage(@MessageBody body: string): Promise<any> { return null; }
You can then invoke this method to send a message to this particular destination, as shown below:
service.sendMessage('Ohhh, what a goal...!')
- Receiving messages over WebSocket
To receiving messages, you must to subscribe to a destination and provide a callback. After that, received messages will be delivered to this particular callback. Use @Subscribe
and @SubscriptionCallback
decorators as shown in the example below.
@Subscribe("/soccer/news")
subscribe(@SubscriptionCallback callback: Function): Promise<any> { return null; }
You can then receive messages sent to /soccer/news
simply by doing this:
service.subscribe((message: string) => { console.log(`New message: ${message}`); })
- Force disconnection
To disconnect from the server you must annotate a method with @DisconnectWebSocket
decorator. Then add a parameter to this method and annotate it with @DisconnectionCallback
. This parameter points to a callback function that will be called after closing connection.
@DisconnectWebSocket
disconnect(@DisconnectionCallback callback: Function): Promise<any> { return null; }
After declaring a method like this, you can close connection with:
service.disconnect(() => { console.log(`DISCONNECTED`); })
- Handling Promises
Remember that all WebSockets methods (as we shown earlier for Http methods) always return a Promise. So, if you need to execute an action just after a WebSocket operation has finished, you should use then
and catch
clauses. You can see an example below.
service.sendMessage('Ohhh, what a goal...!')
.then(() => {
// actions to be done just after sending message ...
})
.catch((error: GatewayServiceError) => {
console.log(`
Your message could not be delivered.
Message: ${error.getMessage()}
`);
});