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.
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.
npm i strongly
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;
}
}
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);
}
})
);
ts-node ./src/app.ts // or - tsc & node ./dist/app.js
open http://localhost:3000/api-doc to see the result.
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: "a@b.c", password: "password" } } );
expect(res.json()).toStrictEqual({ fName: "lo", lName: "asbaba" });
}
- Server
- Controllers
- Route decorators
- Route parameter decorators
- Validation
- Guard decorator
- OpenAPI (Swagger)
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.
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 {}
@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 }) {}
- @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"}
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) {}
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.
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
- version - your package version
- title - your package name
- description - your package description
@Controller("auth", { description: "User authentication stuff" })
/**
* just add comment upon your route method
*/
@post someRoute() {}