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
- Controllers
- Route decorators
- Route parameter decorators
- Validation
- Guard decorator
- OpenAPI (Swagger)
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() {}