I am working on Sandra as a backend engineer, constructing a full-restified API. I found Koa is quite a satisfying solution, but not enough. As known to all, REST specification utilizes a set of HTTP features to accomplish schematic features. A good case in point is that if the client requests with
Accept: application/json, then the server is expected to return a JSON content. However, in Koa, such features should be implemented on my own, and no other frameworks, even the Hapi, provides a simple enough API to fulfill the requirement. So I have decided to develop my own REST API framework, Ulla, both resolving such problem and utilizing the decorator features in TypeScript.
Previously, we call functions on a Router object to construct routes, but such method creates extra codes, so I think use Decorators may be a better approach.
Take Koa for example:
By contrast, my design is:
First, the developer creates a class representing a set of routes with the decorator
RouteSet. The RouteSet can be prefixed with a path, e.g.
Then, the developer adds static functions to the RouteSet for specific routes with the decorator
This design defines routes without any code or extra object creation, which clearifies the code and improves readability.
import statements to explicitly load the routing scripts.
For example, add this line in the main entrypoint before importing Ulla:
/routes/index.js can be:
I purpose to use parameter decorators for parameters, and the route handler can just simply define any parameters it likes. For example:
And when the function takes no parameters, the wrapper can be omitted.
When no wrapper defined, but parameters taken, the parameters will all be
This feature is especially useful when connecting DAO with REST APIs. For example:
This makes a connection between DAO and API, with no extra code.
Most times, we may get confused when trying to modify a response which has been already sent, so by my design, there is only one chance to respond data. The framework will take the return value of the handler function and intercept its content as the respond data.
The following code descibes what the return value should looks like.
For example, a typical 404 Not Found can be constructed with the following code:
As described previously, handling HTTP schematic headers is part of the REST specification, but unfortunately, hardly does modern REST API framework implements these features, and the developers have to figure it out on their own. My design provides helpers to accomplish it, making fulfilling specification much easier, while keeping the framework still tiny and extensible.
Take handling various content MIME types for example, the developer may load a external module to handle the serialization and deserialization.
By such approach, the REST API server is able to handle JSON request contents and return JSON responses.
HTTP specification defines various authorization types, for example, Basic Auth and Bearer Auth. These headers should be also parsed.
Developers may define chained handler for an authorization type, and the returning value is passed as the first parameter of each handler. The return value of the last handler is sent to
HTTP has also a header of
Accpet-Language, which is quite useful for i18n. Tranditionally, developers have to write their own middleware for i18n, but I think these works can be done more simpler.
The framework, will automatically call
LocalizedString#toString with locale code as the first parameter.
As for dates, as there already exists
Accept-Encoding, I think it is better practice to implement it on a load balancer, but in some cases, the support is also necessary in the framework. As content types, this is done via a similar way.
Global middlewares are of great usage, so the framework must provide such features. As middlewares differs from route handlers, it is also defined with a different way. IMO, such defining method is more readable.
Sometimes, we also want middlewares applied at Route or RouteSet level, but as a different approach is used to define routes, the definition of middleware at Route or RouteSet level cannot be the same as global middlewares.
I have figured out a seem-complex approach, to define middlewares in decorators. The developer may apply middlewares as below:
Moreover, according to the DRY priciple, I think middlewares can be pre-defined.
Take note that
UllaApp.buildMiddleware takes only one parameter of a function, which takes the parameter of the desired decorator and returns a middleware.
Then it can be applied to either a specific route or a RouteSet.
This is quite a simple design I want to accomplish, and as development gone through, I may add detailed information on the framework. If any suggestions, you are welcomed to contact me via email at the about page. Please look forward to my framework!