At the beginning of last year, I was interested in Node.js, and I used a lot of Node.js frameworks, but the development experience was not particularly good. I had used a lot of server-side frameworks before, and then GOT to know Node.js. Compared with JS, it was not easy to design a server-side framework. I don’t want to write in typescript and ES6 is more friendly for beginners. Then I developed a Node.js Web framework by myself, and I have time to write articles (manual head) soon. Here to share with you the development experience.


Note: the catalogue is just for good looking, what to think of what to write, no writing at all, small white text.


The selection

As for the framework base layer, I thought about developing one by myself (the cost was too high and ecological issues were considered), but I rejected it. Then I compared Koa2 and Express and finally chose Koa2 as the default base layer (finally, due to the framework architecture design, Koa2 service or Express can be used as the base layer library ๐Ÿคฃ). However, the KOA2 default integration was chosen.

If you choose KOA2, then it’s also a koA2-compatible ecosystem


Architecture design

Just started development is along the road of koa, koa as bottom, extensions of koa CTX, then found that encapsulate a koa buckets of seemingly meaningless (a lot of evening, made the wheel doesn’t make sense, and other frameworks have what distinction, please forgive me this old-fashioned idea ๐Ÿคช), and then start thinking about ๐Ÿค” : The original intention and significance of doing this framework.

As a backend, I naturally thought it would be more elegant to use the IOC container as the underlying layer, but JS does not have type constraints, interfaces and other features. I have also seen many typescript implementations (not significantly different from other backend frameworks, I am not joking, other languages are more complete, more secure and better). I made up my mind to write a JS (ES6) version (cheaper to learn, better to get started with ๐Ÿคญ), and so began my Node.js journey.

It’s off the mark. Here is the architecture of the framework:

With the container as the bottom layer, application classes integrate and bind to the container base classes, acting as application objects and the nanny of the container.

All other services (including KOA, Router, Logger, validate, Request, Response, and so on) are registered in the application (and actually bound to the container) as providers.

Container development

Early didn’t want to so much, development of container is also very well, in the design of the dependency injection pattern (with no interface), to avoid a lot of schemes, finally decided to use a decorator (sweet), not ts, used in the draft ECMA decorator (using the Babel transcoding), 1.0 after finalized, will become the alternative, decorators can increase development experience, But it is not required, and is highly recommended.

For example we open an endpoint (route), decorator example, not injection:

@Router('users')
class UserController {
    @Get()
    index() {
        // ...}}Copy the code

Developed in this pattern, the above example opens up a GET/Users access endpoint

How about injection:

class UserController {
    @Config() config;
    
    @Request()
    index(request) {
        this.request.param()
        this.config.get('app.port')}}Copy the code

We can inject properties, methods, constructors

Principle is to use adornment tag controller attribute methods need injection parameters, and then call a function of time from the container take out (here met a hole), as a result of the HTTP service request context in the callback function, so I bind a callback function to the container, the need for instance context into the function, Generate an instance of, for example, the Request object.


The provider

In the framework developed at this time, all kinds of services are bound in the container by default and coupled with the application class. Although they are the services of the framework, they are still not perfect. Therefore, we draw lessons from the design mode of the provider to extract all services and design an API for registering services.


This way, all of our services are completely decoupled from the underlying core of the framework, ensuring that the underlying core is lean and scalable.


modular

Since it is a Web framework, it will certainly bear different businesses when used, so we need to use modular functions to split businesses and improve maintainability, such as setting which controllers the module contains (supporting wildcards), which middleware the module needs to load, and even sub-module functions

So I came up with a scheme that uses module description classes to define modules

module.exports = class ExampleModule {
    // Identifies submodules
    modules = [];
    
    // Indicate the controller to be loaded
    controllers = [];

    // Identifies the middleware to load
    middlewares = [];
}Copy the code

๐Ÿค“ good

request

As a Web framework, you need to parse requests and things like that. Instead of extending CTX properties, my solution is to use the Request class to parse CTX. The advantage of this is that I can parse KOA’s CTX, Express’s REQ, RES, and other framework context objects. And this class is registered in the container, so if you have another resolution, you can of course register one and do whatever you want (yes, the IOC container can do whatever you want ๐Ÿ˜Ž).

The response

It should be as easy as possible to produce a Response, so that you can just return from the controller. You can return all kinds of data types, in addition to the data types that koA2 supports, you can also directly return a View instance or a Response instance of the frame, etc. The framework automatically decides.

class UserController {
    index() {
        return [{ id: 1}}}]Copy the code

It’s that convenient, but there’s more to it than that.

The validator

I used a lot of frameworks where it was not very convenient to validate the request data, so I redesigned a solution (dog head),

Use decorator mode again, of course

// Define a validation class and place it in the specified directory
class UserPostValidate extends Validate {
    @MaxLength(10) username;
    @Length(1.20) password:
}Copy the code

And then in the controller

class UserController extends Controller {
    store() {
        this.request.validate('UserPostValidate')}}Copy the code

The head

In fact, there are many functional modules, such as log service, multi-process service, interprocess communication service, security related service, cookie and session service, etc., interested in the message can continue to answer, or out of the second article, next time out of the technical class ~ ~ ~ ๐Ÿถ

Warehouse address: [Github]

Of course, the framework of the core code in the framework of the package, is still under development and testing, optimization of some functions and documents, tools are relatively low force, also need to improve, hope to have a big bull can develop together.

It is currently at version 0.8.x, six months in the making (the first NPM package submission), not far from the first stable release ๏ผ๏ผ๏ผ๏ผ Is on the fast track and hopes to make version 1.0 available to the public as soon as next year.

We also hope that you can try to provide all kinds of feedback. Since we are currently developing alone, there must be a lot of bugs. The CLI tool has not been updated for a long time, so we are ready to improve the tool next step.

Then, I hope you don’t be stingy with your own star ๐Ÿถ๐Ÿถ๐Ÿถ๐Ÿถ๐Ÿถ๐Ÿถ๐Ÿถ๐Ÿถ to give encouragement ~ ~ ~ ~

Finally, I wish you a happy New Year and a New Year’s day ๐ŸŽ‰๐ŸŽ‰๐ŸŽ‰๐ŸŽ‰ college ๐ŸŽ‰