Skip to content

Node.js Framework on top of fastify js. It helps you build your server-side application easily with decorators. With strongly js you don't need to add type validation, we do it for you on the fly.

Notifications You must be signed in to change notification settings

yantrab/strongly

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

strongly

Node.js Framework on top of fastify js. It helps you build your server-side application easily with decorators. With strongly js you don't need to add type validation, we do it for you on the fly.

the motivation

All of us doing type validation in the server, we don't want to make any expensive think for free, for example if your api return the user by id from the database, and client sent invalid id type, you don't want to do database query at all.

So, probably you do something like:

const schema = Joi.object().keys({
    username: Joi.string().required(),
    email: Joi.string().email().required()
});

The question is, if I already declare the parameters type, why should I do it twice?

This package will help you to avoid this annoying things and let you focus on the really important work.

Get started

install

npm i strongly

create your controller:

import { body, post, get, params, min, email } from "strongly";

class Contact {
    address?: string;
    id: number;
}
class UserDetails {
    @min(10)
    name: string;
    somePrimitiveArray?: string[];
    contacts: Contact[];
}

export class ShowCaseController {
    /**
     * id is required in the param and should by number
     */
    @get("getUser/:id") getUser(@params params: { id: number }) {
        return { name: "saba" };
    }

    /**
     * this is the same as previous one, with convenient way
     */
    @get("getUsers2/:id") getUsers2(@params("id") id: number) {
        return { name: "saba" };
    }

    /**
     * you can add validation as you want
     */
    @post login(@body("email") @email email: string, @body("password") @min(6) password: string) {
        return { name: "saba" };
    }

    /**>
     * you can add validation on the class,  name should be ta least 10 letters
     */
    @post saveUser(@body user: UserDetails) {
        return user;
    }

    /**
     *  or send your schema validation
     */
    @post saveContact(@body<Contact>({ properties: { address: { maxLength: 10 } } }) contact: Contact) {
        return contact;
    }
}

create the server:

ServerFactory.create({
  controllers: [ ShowCaseController ] /* controllers / path to the controller, or nothing if your controllers located in controllers folder **/
}).then(app =>
  app.listen(3000, (err, address) => {
    if (err) {
      console.log(err);
      process.exit(1);
    }
  })
);

run your app

ts-node ./src/app.ts // or - tsc & node ./dist/app.js

open http://localhost:3000/api-doc to see the result.

Dependency injection

just add your dependencies to the constructor params:

export class AuthController {
    constructor(private userService: UserService) {}
    @post login(@body("email") @email email: string, @body("password") @min(6) password: string) {
        return this.userService.validateAndGetUser(email, password);
    }
}

use @mock decorator:

@test("should return mocked user")
@mock(UserService, "validateAndGetUser", { fName: "lo", lName: "asbaba" })
async login() {
    const res = await this.app.inject({ method: "POST", url: "/auth/login", body: { email: "[email protected]", password: "password" } } );
    expect(res.json()).toStrictEqual({ fName: "lo", lName: "asbaba" });
}

Documentation

Server

create Fastify server instance

ServerFactory.create(options)

options: FastifyServerOptions & { controllers, providers}

  • controller - your controllers or path to your controllers or nothing when your controllers is under controllers' folder.
  • providers - services that you want to inject.

return Fastify server instance.

Controllers

controller is group of routes that handle the http request/response. actually you don't need to decorate your controllers with @controller decorator. we are taking the base path from the class name, with punctuation, the base path for ShowCaseController for example will be "show-case".

if you want to set another url postfix you can pass it to the controller decorator -

@Controller("base-path")
class SomeController {}

Route decorators

@get
@head
@post
@put
@delete
@options
@patch

we are taking the route path from the method name, with punctuation

// the path for this route is /save-user
@post saveUser(@body user: UserDetails) {}

or specify your prefer path

@get("getUser/:id") getUser(@params params: { id: number }) {}

Route parameter decorators

  • @body - request.body - parameters - (path: thePath)
  • @query - request.query
  • @params - request.params
  • @headers - request.headers
  • @user - request.user
  • @request - request
  • @reply - reply
  • @app - Fastify server instance

examples

// request.query
@get getUser(@query query: { id: number }) {}

// request.query.id
@get getUser(@query("id") id: number) {}
  • string - {allOf:[{ transform: ["trim"] }, { minLength: 1 }], type: "string"}
  • number - {type: "number"}

Validation

Fastify uses a schema-based approach, and using ajv by default. we are build the schema from your types -

  • string - {allOf:[{ transform: ["trim"] }, { minLength: 1 }], type: "string"}
  • number - {type: "number"}
  • boolean - {type: "boolean"}

you can add extra validation -

  • send the schema to the route param decorators:
     saveContact(@body<Contact>({ properties: { address: { maxLength: 10 } } }) contact: Contact)
  • or add validation decorator to your model:
    class UserDetails {
      @min(10) name: string;
    }

available extra validation decorators:

- min/max can be on string, number, or array.
- all [format](https://ajv.js.org/docs/validation.html#formats) string validation

example -

// email prameter should be email formatm, and password length should be more then 5 letters
login(@body("email") @email email: string, @body("password") @min(6) password: string) {}

Guard decorator

gourd decorator add pre handler hook to validate that the user have permission. param - (user) => boolean you can decorate class to method that you want to protect:

@guard(user => user.role === "editor")
class a {
  @guard(user => user.isAdmin)
  @get b () {
    return 1;
  }
}

be aware that you need to add the user to the request by you own, you can use fastify-jwt to do it. see here full example.

OpenAPI - Swagger

Like ajv schema we are build the open api schema from you types. just open http://localhost:3000/api-doc to see it, ####swagger options

  • outPutPath - path where you want to put the swagger specification.
    • if not specified the swagger specification save only in memory
  • uiPath - path to the custom swagger ui html file
    • if not specified we're just using redoc

we take some parameters from yoru package.json file

  • version - your package version
  • title - your package name
  • description - your package description

you can add description to your controller -

@Controller("auth", { description: "User authentication stuff" })

you can add description to your route -

  /**
 *  just add comment upon your route method
 */
@post someRoute() {}

Dependencies

  • fastify - a web framework to serve your routes
  • ts-morph - TypeScript Compiler API wrapper to parse your types to ajv & swagger schemas.

About

Node.js Framework on top of fastify js. It helps you build your server-side application easily with decorators. With strongly js you don't need to add type validation, we do it for you on the fly.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published