Research
Security News
Malicious npm Packages Inject SSH Backdoors via Typosquatted Libraries
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
nest-standard-response
Advanced tools
$ npm install nest-standard-response
imports
arrayapp.module.ts
import { StandardResponseModule } from 'nest-standard-response';
@Module({
imports: [
StandardResponseModule.forRoot(options), // options can be ommited
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Check out the options that this module accepts in the Advanced Configuration section.
By default, all routes are automatically wrapped in a standard response object:
|
|
To skip wrapping a particular route, just decorate the handler with @RawResponse().
It's possible to invert this behavior to not wrap any route automatically, and only wrap routes annotated with @StandardResponse() instead. Check out how.
So interceptors like ClassSerializer
and RoleSerializer
work transparently without any custom logic.
Just decorate a route with @StandardResponse({...options}) and pass in the options you want. Adding features will:
To access this information during the request, use the @StandardParam() parameter decorator to inject a params object into your handler. This object contains the parsed query params, all the configuration values you set in the @StandardResponse()
, plus methods to manipulate and add data into the response.
|
|
Features can be freely combined, or used all at once.
For example, using the features shown bellow, the route could be called like this:
/books?limit=8&offset=16&sort=-author,title&filter=author^=Frank;year>=1960;year>=1970
Note: This url was NOT url-encoded for readability (but you would need to encode yours)
|
|
For detailed information on the objects generated by filtering and sorting, as well as a list of all operations available, see the documentation for the @StandardParam() decorator.
A decorator that wraps the return of a route into a standardized API response object (while still allowing the handler to return true DTOs or other model class instances — this makes interceptors like caching, ClassSerializer
, or RoleSerializer
work transparently.)
The wrapper allows custom messages to be set in the response, and has optional features to handle common tasks, like pagination, sorting and filtering.
It can also optionally apply swagger's documentation, providing the correct combined schema for the DTO and the wrapper including any of its features. If given an array of Roles, it can also build Swagger route response examples for each user role, containing the reponse as it would be serialized for that user group.
import { UserDto } from './dto/user.dto';
@Controller('users')
export class UsersController {
constructor(
private readonly usersService: UsersService,
) {}
@Get('/')
@StandardResponse({ type: [UserDto] })
async findAll(): Promise<UserDto[]> {
const users = await this.usersService.findAll();
return users // <--- returns an array of UserDtos
}
}
// get /api/users
// Response:
{
"success": true,
"isArray": true,
"data": [
Users... // <--- The returned array is delivered inside the data property
]
}
(TODO image of swagger UI with the response examples dropdown open. Comparing a response for User and Admin, with arrows showcasing the extra fields returned only to admin)
Option | Type | Description |
---|---|---|
type | Class | The class that represents the object(s) that will be returned from the route (for example, a Model or a DTO). This option is required to get auto-documentation. |
description | string | Used as the desciption field of the response in the OpenAPI docs. |
isPaginated | boolean | Mark the route to serve paginated responses, and allow the use of pagination options. This will capture and validate limit and offset query parameters, and make them available in the handler via @StandardParam . Also sets up pagination fields in the response object. |
isSorted | boolean | Mark the route to serve sorted responses, and allow the use of sorting options. This will capture and validate the sort query parameter, and make it available in the handler via @StandardParam . Also sets up sorting fields in the response object. |
isFiltered | boolean | Mark the route to serve filtered responses, and allow the use of filtering options. This will capture and validate the filter query parameter, parse it into a FilteringQuery , an and make it available in the handler via @StandardParam . Also sets up filtering fields in the response object. |
defaultLimit | number | (Pagination option) The value to used for limit if the query param is missing. (Defaults to 10) |
maxLimit | number | (Pagination option) The maximum value accepted by the limit query param. |
minLimit | number | (Pagination option) The minimum value accepted by the limit query param. |
sortableFields | string[] | (Sorting option) A list of fields that can used for sorting. If left undefined, all fields will be accepted. An empty array allows no fields. |
filterableFields | string[] | (Filtering option) A list of fields that can used for filtering. If left undefined, all fields will be accepted. An empty array allows no fields. |
The default behavior of StandardResponse is to wrap the response from all routes application wide. This keeps the API consistent and predictable. However, if you need to skip this behavior for a particular route, just set the @RawResponse()
decorator:
@Controller('external-api-integration')
export class ExternalApiIntegrationController {
@Get('/')
@RawResponse() // <--- will skip wrapping
async findAll(): Promise<SomeCustomObject> {
return customObject;
}
}
If you're adding StandardResponse into an existing app, it might be useful to invert this behavior to create a gradual transition path. To do this, set the interceptAll
option to false
when importing the StandardResponseModule
in your application. This way, routes will only be wrapped if they have explicitly set the @StandardResponse()
decorator. See more information in the "Configuring" section bellow.
A parameter decorator used to inject a StandardParams
object in the route handler.
This object allows access to:
@StandardResponse()
;import { UserDto } from './dto/user.dto';
@Controller('users')
export class UsersController {
constructor(
private readonly usersService: UsersService,
) {}
@Get('/')
@StandardResponse({
type: [UserDto],
isPaginated: true,
maxLimit: 24,
defaultLimit 12,
})
async findAll(
@StandardParam() params: StandardParams // <--- inject into handler
): Promise<UserDto[]> {
const [users, count] = await this.usersService.findAll({
limit: params.pagination.limit,
offset: params.pagination.offset,
});
params.setPaginationInfo({ count: 348 }) // <--- set additional info
return users;
}
}
// get /api/users?limit=15&offset=30
// Response:
{
"success": true,
"isArray": true,
"isPaginated": true,
"pagination: {
count: 348, // <--- added inside the handler
limit: 15, // <--- from query
offset: 30,
maxLimit: 24, // <--- from decorator options
defaultLimit: 12,
}
"data": [
Users...
]
}
The params object injected with @StandardParam() contains these keys:
Key | Type | Description |
---|---|---|
pagination | PaginationInfo | Only available when the response isPaginated option is true . |
sorting | SortingInfo | Only available when the response isSorted option is true . |
filtering | FilteringInfo | Only available when the response isFiltered option is true . |
setPaginationInfo() | (info: {}) => void | Allows modifying the pagination metadata inside the route handler to add extra information or to reflect some dynamic condition. For example, to add a pagination count . The object passed to this method will be merged with the current information, so partial updates are OK. |
setSortingInfo() | (info: {}) => void | Allows modifying the sorting metadata inside the route handler. |
setFilteringInfo() | (info: {}) => void | Allows modifying the filtering metadata inside the route handler. |
setMessage() | (message: string) => void | Allows setting a custom message in the response object. |
Property | Type | Description |
---|---|---|
query? | string | The original string from the request for the limit and offset query params. [ReadOnly] |
limit? | number | How many items to send. This is the same as the limit query param, but parsed and validated. |
offset? | number | How many items to skip. This is the same as the offset query param, but parsed and validated. |
count? | number | The total count of items that are being paginated. This value needs to be set inside the handler using the setPaginationInfo() method. |
maxLimit? | number | The maximum value accepted by the limit query param. [ReadOnly] (From the options set in @StandardResponse() ). |
minLimit? | number | The minimum value accepted by the limit query param. [ReadOnly] (From the options set in @StandardResponse() ). |
defaultLimit? | number | The default number of items to send if no query limit is provided. [ReadOnly] (From the options set in @StandardResponse() ). |
Property | Type | Description |
---|---|---|
query? | string | The original string from the request for the sort query param. |
sortableFields? | string[] | A list of all the fields that can used for sorting. [ReadOnly] (From the options set in @StandardResponse() ). |
sort? | SortingOperation[] | An array of SortingOperation objects parsed from the query. |
SortingOperation | ||
field | string | The name of the field being sorted. |
order | 'asc' | 'des' | Order of the sorting operation. These strings are available in an enum for static typing: SortingOrder.ASC and SortingOrder.DES . |
Property | Type | Description |
---|---|---|
query? | string | The original string from the request for the filter query param. |
filterableFields? | string[] | A list of all the fields that can used for filtering. [ReadOnly] (From the options set in @StandardResponse() ). |
filter? | { allOf: FilteringQueryGroup[] } | Filter is an object parsed from the query containing a single property: allOf. This is an array of FilteringQueryGroup objects. All of these filter groups should be combined using an AND operation. |
FilteringQueryGroup | ||
anyOf | FilteringQueryOperation[] | An array of FilteringQueryOperation objects. These filters should be combined using an OR operation. |
FilteringQueryOperation | ||
field | string | Name of the field to filter on. |
operation | string | The comparison operation to perform. Possible operators are bellow. |
value | string | Value used for the comparison. |
Operation | Description | URL Encoded Form | Example |
---|---|---|---|
== | Equals | %3D%3D | . |
!= | Not Equals | !%3D | . |
<= | Less than or equal to | %3C%3D | . |
< | Less than | %3C | . |
>= | Greater than or equal to | %3E%3D | . |
> | Greater than | %3E | . |
=@ | Contains | %3D@ | . |
!@ | Does not contain | !@ | . |
=^ | Starts with | %3D%5E | . |
=$ | Ends with | %3D%24 | . |
These rules are similar to other APIs like Google Analytics or Matomo Analytics.
When building a query, all AND operations should be separated by a semicolon (;), and all OR operations should be separed by a comma (,). For example:
This query will filter all books available for lending, which were first published in France OR Italy, between 1970 AND 1999, whose author starts with Vittorio OR ends with Alatri:
available==true;country==France,country==Italy;year>=1970;year<=1999;author=^Vittorio,author=$Alatri
The resulting parsed object from this query will be:
{ allOf: [
{ anyOf: [
{ field: 'available', operation: '==', value: true },
]},
{ anyOf: [
{ field: 'country', operation: '==', value: 'France' },
{ field: 'country', operation: '==', value: 'Italy' },
]},
{ anyOf: [
{ field: 'year', operation: '>=', value: 1970 },
]},
{ anyOf: [
{ field: 'year', operation: '<=', value: 1999 },
]},
{ anyOf: [
{ field: 'author', operation: '=^', value: 'Vittorio' },
{ field: 'author', operation: '=$', value: 'Alatri' },
]},
]}
Allows you to provide a validation function to stop the return of a route if certain conditions are met.
For example: this can abort a request if a route tries to return — instead a DTO — a raw DB document or some other object that may leak information not intended to be exposed.
This function should return false
to abort the request.
@Module({
imports: [
StandardResponseModule.forRoot({
validateResponse: (data) => {
if (isMongooseObject(data)) return false;
return true;
},
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Setting interceptAll
to false
will invert the default behavior of wrapping all routes by default, and will instead only wrap routes decorated with @StandardResponse()
.
@Module({
imports: [
StandardResponseModule.forRoot({
interceptAll: false
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
setExtra(field, value)
;🏭 ⭐️ 🕹️ 💡 💎 🔩 ⚙️ 🧱 🔮 💈 🛍️ 🎁 🪭 ⚜️ ❇️ 🚩 📦 🏷️ 📮 🟠 🟧 🔶 🔸
FAQs
Standardized and configurable API responses for NestJS
The npm package nest-standard-response receives a total of 163 weekly downloads. As such, nest-standard-response popularity was classified as not popular.
We found that nest-standard-response 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.
Research
Security News
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.