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

tsrocket

v0.1.4

Published

Ease up web APIs development in Typescript with scaffolding, dependency injection and some sweet decorators

Downloads

25

Readme

tsrocket 🚀

Framework for web APIs development in Typescript with scaffolding, dependency injection and some sweet decorators 🍭

What's tsrocket?

tsrocket is a lightweight REST framework with dependecy injection, CLI and code scaffolding. The ideia is to offer a well defined project strucuture for API development. A tsrocket project has four layers: controller, model/DTO, repository and service.

Controller layer

The Controller layer is responsible for handling incomming HTTP requests and provide a suitable response.

Model and DTO layer

The Model layer represents the domain model. In tsrcoket, database-backed model classes are TypeORM's entities.

DTO stands for Data Transfer Object. In tsrocket, DTOs represent what the controller should expect as inputs in the requests. Using DTOs, we can validate if the controller is receiving the data it's expecting, avoiding runtime errors.

Repository layer

The Repository layer is a collection of TypeORM custom repository. Any model manipulation or validation should be included in it.

Service layer

All business logic of your application should reside in the Service layer. tsrocket has a built-in service injection inspired by typedi and typeorm-typedi-extensions.

Quick Start

The first thing we need to do is to install the tsrocket package. tsrocket was developed and tested for node version >=12.14.1

npm install -g tsrocket

Then, we can use tsrocket's cli tsr to create an application. To do so, run the following command:

tsr new -y sample-api

tsrocket will generate a project structured as follows with everything configured for your application to run in development mode.

sample-api/
├── src/
│   ├── controllers/
│   ├── dtos/
│   ├── migrations/
│   ├── models/
│   ├── repositories/
│   ├── services/
│   ├── config.ts
│   └── server.ts
├── tests/
├── package.json
├── tsconfig.json
└── README.md

If we open the file src/server.ts:

// src/server.ts
import { Server } from 'tsrocket'
import config from './config'
import { createConnection } from 'typeorm'

async function main() {
    const connection = await createConnection(config.database)
    const server = new Server(config)
    await server.init(connection)

    server.listen()
}

export default main()

This is the main file of our application and we can run it in development mode with the command npm run start:dev. It basically creates a database connection and tells tsrocket to setup your application so it can be served.

$ cd sample-api
$ npm run dev
info: listening at port 3000

By default, tsrocket uses sqlite as database. We may want to change it to run the application in production. To do so, we can update the src/config.ts file. The TypeORM connection documentation might help.

We can run tsr -help if we get stuck.

To disable CORS, we can use: server.disableCors().

Model and Repository generation

Suppose we want to generate a User model with a required field 'name' and an optional 'email'. To do so, we can run tsr g model user name:string 'email?:string'. The g command stands for generate.

tsrocket will generate a TypeORM entity:

// src/models/user.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'

@Entity()
export default class User {

    @PrimaryGeneratedColumn()
    id: number

    @Column()
    name: string

    @Column({ nullable:true })
    email?: string

}

A TypeORM repository:

// src/repositories/post.ts
import { EntityRepository, Repository } from "typeorm";
import User from "../models/user";

@EntityRepository(User)
export default class UserRepository extends Repository<User> {
    /* Add you model logic inside here */
}

And a TypeORM migration file inside the migrations folder. To apply the migration, we can run npm run orm migration:run

We can run tsr generate model --h to get more information about tsr model generation:

Service and Controller generation

After we generate the User model and its repository. We can use tsrocket cli to generate a service to manipulate the repository. Run tsr g service user to generate the following file:

// src/services/user.ts
import { Service } from 'tsrocket'

@Service()
export default class UserService { }

We can use use the @InjectRepository decorator to inject the user repository and @Inject to inject another service.

// src/services/user.ts
import { Service, InjectRepository } from 'tsrocket'
import UserRepository from '../repositories/user'
import AnotherService from './another-service'

@Service()
export default class UserService {

    @InjectRepository(UserRepository)
    private readonly repository: UserRepository
    
    @Inject(AnotherService)
    private readonly anotherService: AnotherService

}

It's also possible to use a factory to dynamically use a instance when injecting a service. To do so, we need to implement the InjectableFactory interface. This is useful when we want to use mocked services when testing for example:

import { InjectableFactory, Service } from 'tsrocket'

class UserServiceFactory implements InjectableFactory {
    getInstance(): Object {
        return process.env['ENV'] === 'test'
            ? new MockedUserService()
            : new UserService()
    }
}

@Service(UserServiceFactory)
export default class UserService { }

In many aplications, we need a service to handle basic CRUD operations using the model repository. We can run tsr g model -s user name:string 'email?:string' to generate a model with its repository, migration and CRUD ready service.

If we run tsr g model -c user name:string 'email?:string', besides a user service, tsr will also generate a CRUD controller with everyting configured and ready to run.

Both the flags -s and -c will create DTOs automatically.

For example:

// src/controller/user.ts
import { Controller, Get, RestController, Params, Put, Body, Post, Inject, Delete } from 'tsrocket'
import UserService from '../services/user'
import { UserDto } from '../dtos/user'

@Controller('/users')
export default class UserController extends RestController {

    @Inject(UserService)
    private readonly userService: UserService

    @Get('/')
    index() {
        return this.userService.all()
    }

    @Get('/:id')
    find(@Params() id: string) {
        return this.userService.find(id)
    }

    @Put('/:id')
    update(
        @Params() id: string,
        @Body(UserDto) userDto: UserDto
    ) {
        this.userService.update(id, userDto)
    }

    @Post('/')
    create(@Body(UserDto) userDto: UserDto) {
        return this.userService.create(userDto)
    }

    @Delete('/:id')
    delete(@Params() id: string) {
        this.userService.delete(id)
    }

}

We can use the controller generator to create a controller. Running tsr g controller user user will generate the following file:

// src/controllers/user.ts
import { Controller, Get, RestController, Inject } from 'tsrocket'
import UserService from '../services/user'

@Controller('/users')
export default class UserController extends RestController {

    @Inject(UserService)
    private readonly userService: UserService

    @Get('/')
    index() {
        return 'Hello world from /users'
    }

}

As we can see, tsrocket generated a controller with the user service already injected with the @Inject decorator. The first argument of the tsr g controller command is the name of the controller and any following argument will be treated as dependency injection by the tsrocket scaffold.

Request handler decorators

We can use @Get to indicate a HTTP get request handler, @Post for a post handler and so on. tsrocket knows what HTTP status to send depending on the HTTP method and the handler response (if it returns something valid or throws an error, for instance). We need to pass a path as argument to every of these HTTP method decorators. The @Body and @Params decorators reflects express body and params properties from a Request instance.

Request handler argument validator

We can decorate DTO class properties to validate and make sure that the controller handlers receive the expected data from the request. The @Field is used to indicate an DTO attribute. We can also use class-validator decorators, such as IsString and IsEmail for instance.

If we need to process an incoming data, we can pass a function in the transform option.

// src/dtos/user.ts
import { Field } from 'tsrocket'
import { IsEmail, IsString, IsDate } from 'class-validator'

export class UserDto {

    @Field()
    @IsString()
    name: string

    @Field()
    @IsEmail()
    email: string

    @Field({ transform: (value: string) => new Date(value) })
    @IsDate()
    birthDate: Date

    @Field({ nullable: true })
    @IsString()
    lastName?:string

}

TypeORM integration

If we need to, we can run npm run orm to execute typeorm commands. For instance, we can apply the migrations by typing the following command in the terminal npm run orm migration:run.

Response Interceptor

By default, tsrocket uses the following structure to respond all the requests:

{
    "data": { },
    "error": { }
}

We can change this behaviour in two different ways: controller and application level.

The first step is to implement a ResponseInterceptor class:

class CustomInterceptor implements ResponseInterceptor {

    intercept(response?: any, error?: Error) {
        console.log('Decorating the request response')

        const decoratedResponse = {
            response,
            error
        }

        return decoratedResponse
    }

}

So the response will be:

{
    "reponse": { },
    "error": { }
}

To apply this interceptor to a specific controller, we can use the @UseResponseInterceptor decorator:

// src/controllers/cars.ts
@UseResponseInterceptor(CustomInterceptor)
@Controller('/cars')
export default class CarController extends RestController { }

Note that, we only need to pass the class as argument. Internally, tsrocket uses its dependency injection mechanism.

Or we can use the CustomInterceptor in the application level. In this case, the interceptor will be attached to every controller:

// src/server.ts
const server = new Server(config)
server.useResponseInterceptor(CustomInterceptor)

Response Mapper

Sometimes we may need to filter what the controller will send as response. In tsrocket it's quite easy and straightforward. We only need to implement a DTO class. For example, suppose we have a user model:

import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'

@Entity()
export class User {

    @PrimaryGeneratedColumn('uuid')
    id: string

    @Column()
    name: string

    @Column()
    email: string

    @Column()
    password: string

}

We can see that User has multiple properties but we want to return only the name and email. So the resulting DTO would be:

import { Field } from 'tsrocket'

export class UserResponseDto {

    @Field()
    name: string

    @Field()
    email: string

}

To map a controller handler response we only need to pass the DTO class as parameter to the HTTP method decorator. @Get in the example below. The handler response can be either a object or an array of objects.

@Controller('/users')
export class UserController {

    @Inject(UserService)
    private readonly userService: UserService

    @Get('/', UserResponseDto)
    listUsers() {
        return this.userService.find()
    }

}