
Product
Announcing Socket Fix 2.0
Socket Fix 2.0 brings targeted CVE remediation, smarter upgrade planning, and broader ecosystem support to help developers get to zero alerts.
Ease up web APIs development in Typescript with scaffolding, dependency injection and some sweet decorators
Framework for web APIs development in Typescript with scaffolding, dependency injection and some sweet decorators 🍭
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.
The Controller layer is responsible for handling incomming HTTP requests and provide a suitable response.
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.
The Repository layer is a collection of TypeORM custom repository. Any model manipulation or validation should be included in it.
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.
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()
.
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:
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.
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.
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
}
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
.
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)
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()
}
}
FAQs
Ease up web APIs development in Typescript with scaffolding, dependency injection and some sweet decorators
We found that tsrocket demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Product
Socket Fix 2.0 brings targeted CVE remediation, smarter upgrade planning, and broader ecosystem support to help developers get to zero alerts.
Security News
Socket CEO Feross Aboukhadijeh joins Risky Business Weekly to unpack recent npm phishing attacks, their limited impact, and the risks if attackers get smarter.
Product
Socket’s new Tier 1 Reachability filters out up to 80% of irrelevant CVEs, so security teams can focus on the vulnerabilities that matter.