npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

strongly

v2.0.4

Published

Make your server strongly type without adding any piece of code

Downloads

32

Readme

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.