
Research
Malicious npm Package Brand-Squats TanStack to Exfiltrate Environment Variables
A brand-squatted TanStack npm package used postinstall scripts to steal .env files and exfiltrate developer secrets to an attacker-controlled endpoint.
@pyriter/unrest
Advanced tools
Request routing for AWS Lambda running Nodejs, written in Typescript
Motivation: Existing routing libraries are inefficient. This library uses a trie data structure with local caching to improve lookup and response time. (More latency data to come)
npm install @pyriter/unrest
Set the "noStrictGenericChecks" to true in your tsconfig to avoid typescript errors
{
"compilerOptions": {
...
"noStrictGenericChecks": true,
...
}
}
import { StatusType, Unrest, MethodType } from "@pyriter/unrest";
import { APIGatewayProxyEvent } from "aws-lambda";
import { UnrestResponse } from "./unrestResponse";
import { RequestProps } from "./route";
class ApiServiceHandler {
private readonly unrest: Unrest;
constructor() {
this.unrest = Unrest.builder()
.withRoute({
method: MethodType.GET,
path: "/api/v1/ping",
handler: async (): Promise<Response> => {
return Response.builder()
.withStatusCode(StatusType.OK)
.withBody({
message: "success"
}).build();
}
})
.build();
}
async handle(event: APIGatewayProxyEvent): Promise<UnrestResponse> {
return await this.unrest.execute(event);
}
}
Create controllers to organize your API endpoints:
import { RequestProps, Response, StatusType, Unrest, MethodType } from '@pyriter/unrest';
export class UserController {
constructor(private readonly unrest: Unrest) {
this.unrest.withRoutes([
{
method: MethodType.GET,
path: '/api/v1/users',
handler: this.getAllUsers,
thisReference: this,
},
{
method: MethodType.GET,
path: '/api/v1/users/{userId}',
handler: this.getUserById,
thisReference: this,
},
{
method: MethodType.POST,
path: '/api/v1/users',
handler: this.createUser,
thisReference: this,
},
{
method: MethodType.PUT,
path: '/api/v1/users/{userId}',
handler: this.updateUser,
thisReference: this,
},
{
method: MethodType.DELETE,
path: '/api/v1/users/{userId}',
handler: this.deleteUser,
thisReference: this,
},
]);
}
async getAllUsers(request: RequestProps<undefined>): Promise<Response<User[] | string>> {
try {
const { apiGatewayEvent, queryStringParams } = request;
const { limit, offset } = queryStringParams;
// Your business logic here
const users = await this.userService.getUsers({ limit, offset });
return Response.builder<User[]>()
.withStatusCode(StatusType.OK)
.withBody(users)
.build();
} catch (error) {
return Response.builder<string>()
.withStatusCode(StatusType.INTERNAL_SERVER_ERROR)
.withBody(`Error fetching users: ${error}`)
.build();
}
}
async getUserById(request: RequestProps<undefined>): Promise<Response<User | string>> {
try {
const { urlParams } = request;
const userId = urlParams.userId || '';
if (!userId) {
return Response.builder<string>()
.withStatusCode(StatusType.BAD_REQUEST)
.withBody('userId is required')
.build();
}
const user = await this.userService.getUserById(userId);
if (!user) {
return Response.builder<string>()
.withStatusCode(StatusType.NOT_FOUND)
.withBody('User not found')
.build();
}
return Response.builder<User>()
.withStatusCode(StatusType.OK)
.withBody(user)
.build();
} catch (error) {
return Response.builder<string>()
.withStatusCode(StatusType.INTERNAL_SERVER_ERROR)
.withBody(`Error fetching user: ${error}`)
.build();
}
}
async createUser(request: RequestProps<CreateUserRequest>): Promise<Response<User | string>> {
try {
const { body, apiGatewayEvent } = request;
const { name, email, role } = body;
// Validate required fields
if (!name || !email) {
return Response.builder<string>()
.withStatusCode(StatusType.BAD_REQUEST)
.withBody('Name and email are required')
.build();
}
const user = await this.userService.createUser({ name, email, role });
return Response.builder<User>()
.withStatusCode(StatusType.CREATED)
.withBody(user)
.build();
} catch (error) {
return Response.builder<string>()
.withStatusCode(StatusType.INTERNAL_SERVER_ERROR)
.withBody(`Error creating user: ${error}`)
.build();
}
}
async updateUser(request: RequestProps<UpdateUserRequest>): Promise<Response<User | string>> {
try {
const { urlParams, body, apiGatewayEvent } = request;
const userId = urlParams.userId || '';
const { name, email, role } = body;
if (!userId) {
return Response.builder<string>()
.withStatusCode(StatusType.BAD_REQUEST)
.withBody('userId is required')
.build();
}
const updatedUser = await this.userService.updateUser(userId, { name, email, role });
return Response.builder<User>()
.withStatusCode(StatusType.OK)
.withBody(updatedUser)
.build();
} catch (error) {
return Response.builder<string>()
.withStatusCode(StatusType.INTERNAL_SERVER_ERROR)
.withBody(`Error updating user: ${error}`)
.build();
}
}
async deleteUser(request: RequestProps<undefined>): Promise<Response<boolean | string>> {
try {
const { urlParams } = request;
const userId = urlParams.userId || '';
if (!userId) {
return Response.builder<string>()
.withStatusCode(StatusType.BAD_REQUEST)
.withBody('userId is required')
.build();
}
await this.userService.deleteUser(userId);
return Response.builder<boolean>()
.withStatusCode(StatusType.OK)
.withBody(true)
.build();
} catch (error) {
return Response.builder<string>()
.withStatusCode(StatusType.INTERNAL_SERVER_ERROR)
.withBody(`Error deleting user: ${error}`)
.build();
}
}
}
Wire up your controllers in a main service handler:
import { APIGatewayProxyEvent } from 'aws-lambda';
import { Unrest, UnrestResponse } from '@pyriter/unrest';
export class ServiceHandler {
constructor(
private readonly userController: UserController,
private readonly orderController: OrderController,
private readonly productController: ProductController,
private readonly unrest: Unrest,
) {
// Controllers have configured their routes, build the Unrest instance
}
async handle(event: APIGatewayProxyEvent): Promise<UnrestResponse> {
return await this.unrest.execute(event);
}
}
Access URL parameters using request.urlParams:
async getUserById(request: RequestProps<undefined>): Promise<Response<User | string>> {
const { urlParams } = request;
const userId = urlParams.userId || '';
if (!userId) {
return Response.builder<string>()
.withStatusCode(StatusType.BAD_REQUEST)
.withBody('userId is required')
.build();
}
// Use userId in your business logic
}
Access query parameters using request.queryStringParams:
async getUsers(request: RequestProps<undefined>): Promise<Response<User[] | string>> {
const { queryStringParams } = request;
const { limit = '10', offset = '0', sortBy = 'name' } = queryStringParams;
const users = await this.userService.getUsers({
limit: parseInt(limit),
offset: parseInt(offset),
sortBy
});
return Response.builder<User[]>()
.withStatusCode(StatusType.OK)
.withBody(users)
.build();
}
Type your request body and access it via request.body:
interface CreateUserRequest {
name: string;
email: string;
role?: string;
}
async createUser(request: RequestProps<CreateUserRequest>): Promise<Response<User | string>> {
const { body } = request;
const { name, email, role } = body;
// Validate and process the request body
if (!name || !email) {
return Response.builder<string>()
.withStatusCode(StatusType.BAD_REQUEST)
.withBody('Name and email are required')
.build();
}
const user = await this.userService.createUser({ name, email, role });
return Response.builder<User>()
.withStatusCode(StatusType.CREATED)
.withBody(user)
.build();
}
Use the Response.builder() to construct standardized responses:
// Success response
return Response.builder<User>()
.withStatusCode(StatusType.OK)
.withBody(user)
.build();
// Error response
return Response.builder<string>()
.withStatusCode(StatusType.BAD_REQUEST)
.withBody('Validation error: field is required')
.build();
// Created response
return Response.builder<User>()
.withStatusCode(StatusType.CREATED)
.withBody(newUser)
.build();
// Not found response
return Response.builder<string>()
.withStatusCode(StatusType.NOT_FOUND)
.withBody('Resource not found')
.build();
Implement consistent error handling across your controllers:
async handleRequest<T>(requestFn: () => Promise<T>): Promise<Response<T | string>> {
try {
const result = await requestFn();
return Response.builder<T>()
.withStatusCode(StatusType.OK)
.withBody(result)
.build();
} catch (error) {
console.error('Request failed:', error);
if (error.name === 'ValidationError') {
return Response.builder<string>()
.withStatusCode(StatusType.BAD_REQUEST)
.withBody(`Validation error: ${error.message}`)
.build();
}
if (error.name === 'NotFoundError') {
return Response.builder<string>()
.withStatusCode(StatusType.NOT_FOUND)
.withBody(error.message)
.build();
}
return Response.builder<string>()
.withStatusCode(StatusType.INTERNAL_SERVER_ERROR)
.withBody('Internal server error')
.build();
}
}
The routing library itself. It can execute an APIGatewayEvent and invoke the desired controller.
Returns the builder for creating an instance of the unrest object.
Enum for HTTP methods:
MethodType.GETMethodType.POSTMethodType.PUTMethodType.DELETEMethodType.PATCHEnum for HTTP status codes:
StatusType.OK (200)StatusType.CREATED (201)StatusType.BAD_REQUEST (400)StatusType.UNAUTHORIZED (401)StatusType.FORBIDDEN (403)StatusType.NOT_FOUND (404)StatusType.INTERNAL_SERVER_ERROR (500)Generic interface for request properties:
urlParams: URL path parametersqueryStringParams: Query string parametersbody: Request body (typed as T)headers: Request headersapiGatewayEvent: Original AWS API Gateway eventmethod: HTTP methodpath: Request pathGeneric response interface:
statusCode: HTTP status codebody: Response body (typed as T)Builder pattern for constructing responses:
.withStatusCode(code): Set HTTP status code.withBody(data): Set response body.build(): Build the final responseUnrest uses a trie data structure for efficient route matching and includes local caching to improve lookup and response times. The library is designed to minimize latency in AWS Lambda environments.
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License.
FAQs
Request routing library for NodeJs. Written for AWS lambda.
We found that @pyriter/unrest demonstrated a healthy version release cadence and project activity because the last version was released less than 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
A brand-squatted TanStack npm package used postinstall scripts to steal .env files and exfiltrate developer secrets to an attacker-controlled endpoint.

Research
Compromised SAP CAP npm packages download and execute unverified binaries, creating urgent supply chain risk for affected developers and CI/CD environments.

Company News
Socket has acquired Secure Annex to expand extension security across browsers, IDEs, and AI tools.