Airship
Introduction
Airship is a framework for Node.JS & TypeScript that helps you to write big, scalable and maintainable API servers.
Main features:
- clean and simple architecture
- statically typed
- simple API
- automatic models serialization and deserialization
- automatic API Scheme generation
- automatic SDK generation for the web frontend
- automatic documentation generation
- ability to switch transport protocol (at this moment only HTTP is implemented)
Basic concepts
The main idea is very simple, every request has its model (its just a class), every response has a model too. To handle request there is a component called RequestHandler
, every request handler handles a specific request and returns specific response. Its important that at this point whole system does not even know anything about the network and it should not. Because of that, your system is very abstract, it just handles specified requests and returns specified responses. This gives you the ability to change your network protocol or even stop using your system like a web server and use it as a part of local UI application.
Installation
To install the stable version:
npm install --save airship-server
This assumes you are using npm as your package manager. If you don’t, you can access these files on unpkg, download them, or point your package manager to them.
Basic example
Let's imagine that we need a web server with just one method /randomInt
which returns a random integer in range.
First of all, we need to write our request and response models:
So, it's pretty simple, we just created a class that extends from base request class and have from
and to
properties. The only interesting thing is using of @serializable()
decorator, it's used to have the ability to serialize and deserialize our model.
And we also setting query path using @queryPath
decorator, that string will be used for URL in HTTP & for methods names in SDK
Response model is also very simple and extends from base response model.
Now we need to write a handler for our method, it's also very simple, we just need to write a class that extends from BaseRequestHandler
class and implement two methods:
handle method gets an instance of our request method and returns an instance of response. supports method just tells the system which requests are supported by this handler.
Now we just need to set this up:
server.start
So the top level component is AirshipAPIServer
, you need to pass at least two things to it: requests provider which somehow gets request. In our case, we are using HttpRequestsProvider
, so requests are coming from the network over HTTP at 7000
port. HttpRequestsProvider
needs a logger, the port, and list of supported request models.
The second required argument is requestsHandler
, because in our case we have just one method - we could have been passed just an instance of RandomIntHandler
. But when you have several methods you should use RequestHandlersManager
, that class itself extends BaseRequestHandler
and finds which handler can handle any request, you just need to pass your handlers.
The last thing we need to do is start the server, that's it!
Models serialization
Let's start with example:
Here we have simple Square model, first of all if we want this model to be serializable - we need to implement ISerializable
interface:
Now we need to point our fields using @serializable
decorator:
@serializable
decorator is pretty simple:
serializablename?: string, arrayType?: Function
name - is the name of your property, you can leave it empty & system will automatically detect it, if property name starts with "_" system will replace it with empty string ("_name" -> "name")
arrayType
- since typescript cant provide us type of array items you should pass it yourself, for example if you have property ids: number[] - @serializable
call should look like this:
public ids: number
Now we can serialize and deserialize
our model, there is a BaseSerializer
class which implements common logic and we have JSONSerializer
which extends from BaseSerializer
.
There are two methods that we need:
public static serializeentity: ISerializable: Object public static deserialize serializableType: ISerializable & Function, raw:
First one gets your model instance and serializes in, second one deserializes
it:
JSONSerializer.serializenew Square10, 10// { "width": 10, "height": 10 }
JSONSerializer.deserializeSquare, // Square { width: 10, height: 10 }
deserialize
method also does type checking and existing checking.
Logger
There is a BaseLogger
interface that specifies basic logging capabilities:
You can implement your own logger using this interface or you can use ConsoleLogger
which implements BaseLogger
and writes logs to stdout
& stderr
Handlers
There are two ways to write handlers at this moment. The first option is to extend BaseRequestHandler
:
Because BaseRequestHandler
is on great for handling multiply request - there is an subclass of it called MultiRequestHandler
.
You can use MultiRequestHandler
like this:
All requests are subclasses of ASRequest
and all responses are subclasses of ASResponse
.
There are two already implemented responses:
- ASSuccessResponse
- ASErrorResponse
Cache
There is a BaseCache
abstract class which specifies basic caching capabilities:
You can implement your own cache using this interface or you can use MemoryCache
which implements BaseCache
and stores data in memory (not all methods are implemented in MemoryCache
).
BaseCache
is not used in system, but it's may be useful in your project.
API Server
Main logic for API Server is implemented at AirshipAPIServer
, implementation is pretty simple: it just wait's for requests from RequestsProvider
, passes them to BaseRequestHandler
and returns responses back to RequestsProvider
.
To create server you need to pass config object to AirshipAPIServer
constructor, config object must mach this interface:
Because AirshipAPIServer
uses just one request handler it's expected that handler is capable of handling all types of request of your system.
There is a subclass of BaseRequestHandler
called RequestHandlerManager
for that purposes. RequestHandlerManager
receives array of all of your handlers and passes each request to handler that supports it.
Requests provider
Request provider is a component thet provides requests to AirshipAPIServer
. There is a RequestsProvider
class:
getRequests
method is called by AirshipAPIServer
to subscribe to requests, response for request is returned by AirshipAPIServer
to answerRequest
function.
At this moment we have requests provider for http: HttpRequestsProvider
, it uses dietjs
module for networking.
Usage is pretty simple:
new HttpRequestsProvider logger, // instance of BaseLogger 7000, // port // list of your requests GetUserRequest, SaveUserRequest
Feel free to create more requests providers!
Statistics
If you pass subclass of BaseStatisticsCounter
to AirshipAPIServer
server will call countRequestHit
when request comes and doneRequest
when request handled.
There is a subclass called LocalStatisticsCounter
which prints stats using your logger:
Generating api scheme
What is api scheme? It's a json file which describes your server models, responses and requests. Api scheme is used to generate client adk & docs.
To generate scheme first you need to create config file which implements ApiServerConfig
interface:
endpoints
is just and array of request response pairs.
After you done with that you can use aschemegen
tool to generate scheme:
node_modules/.bin/aschemegen --o=/Users/altox/Desktop/test-server/scheme --c=/Users/altox/Desktop/test-server/build/config.js
o
argument is the absolute path where scheme will be saved
c
argument is the absolute path of compiled config file
Generating client SDK
To generate client SDK you just need to run asdkgen
and pass to it path to your api schemes and output path:
node_modules/.bin/asdkgen --s=/Users/altox/Desktop/test-server/scheme --o=/Users/altox/Desktop/test-server/sdk
SDK is fully statically typed and written in TypeScript, so you can use it in your TS projects and you can compile it and use in your JS projects.
SDK uses fetch
, so you might need some polyfill.
Generating docs
Generating docs is just like generating SDK, but you want to use asdocgen
:
node_modules/.bin/asdocgen --s=/Users/altox/Desktop/test-server/scheme --o=/Users/altox/Desktop/test-server/docs