Lightweight pipeline API constructor
npm install @tripod311/currentsCurrents is a lightweight pipeline-based API constructor, designed to cover 90% of API use cases.
* π¦ Routing
* π CORS headers
* πͺ Cookie management
* π‘οΈ Security headers
* π Static file delivery
* π¦ Parsing of popular request bodies (JSON, multipart, etc.)
* β‘ HTTP/2 support
Currents is pipeline-based: every route is treated as a chain of handlers. Instead of plugin registration, you just add or remove handlers from the chain. This gives you full control over every route.
---
``bash`
npm install @tripod311/currents
---
`ts
import { Currents, Context, JsonBody, MultipartBody, ServeStatic } from "@tripod311/currents"
const app = Currents.fromOptions({});
app.get("/", [
(ctx: Context) => {
ctx.text("Hello world");
}
]);
app.post('/table/:id', [
JsonBody,
(ctx: Context) => {
const body = ctx.body as { some: "body" };
ctx.json({ hello: "world" });
}
]);
app.put('/images/*', [
MultipartBody,
(ctx: Context) => {
// some operations with body
ctx.binary(result_buffer);
}
]);
app.get('/images/', [
ServeStatic({
basePath: '/images',
rootDir: '/home/me/images',
cacheControl: ["public", "max-age=0"],
fallback: 'default.png'
})
])
app.server.listen({ port: 80 });
`
---
The main application class. Holds the server and provides the interface for routing.
#### Constructor
`ts`
Currents.fromOptions({
forceHttpVersion?: 1 | 2,
certificates: {
cert: "path-to-cert",
key: "path-to-key",
ca?: "path-to-ca"
}
})
* Creates the app and sets up the server with given configuration.
* If you donβt force HTTP version: with certificates β defaults to HTTP/2, without β defaults to HTTP/1.
`ts`
Currents.fromServer(server: HttpServer | Http2Server)
* Create Currents app with an already configured server.
#### Methods
`ts`
app.get(route: string, handlers: RouteHandler[])
app.post(route: string, handlers: RouteHandler[])
app.put(route: string, handlers: RouteHandler[])
app.delete(route: string, handlers: RouteHandler[])
app.any(route: string, handlers: RouteHandler[])
#### Properties
`ts`
app.notFoundHandler(chain: RouteHandler[])
Custom 404 handler. By default returns: Cannot find ${route}.
`ts`
app.errorHandler(handler: (err: any, ctx: Context) => void)
Custom error handler.
---
Context represents a single request/response.
#### Properties
* ctx.method: string β Request methodctx.path: string
* β URL-decoded request pathctx.headers: Record
* β Request headersctx.cookies: Record
* β Filled if you use ParseCookiesctx.params: Record
* β Route params (/table/:id)ctx.query: UrlSearchParams
* β Query parametersctx.body: any
* β Filled if body parsers are usedctx.locals: Record
* β Custom data (pass between handlers)ctx.finished: boolean
* β Response already sentctx.notFound: boolean
* β NotFound chain should be executed
#### Methods
* ctx.responseHeader(header, value) β set response headerctx.status(code)
* β set HTTP statusctx.json(value)
* β send JSON with proper headerctx.text(value)
* β send text responsectx.binary(value)
* β send buffer responsectx.send(data, contentLength?)
* β send custom buffer or streamctx.end()
* β end response with empty bodyctx.redirect(to)
* β 307 redirectctx.callNotFound()
* β call default notFound chain
---
Default handlers to populate ctx.body:
`ts`
import { BinaryBody, TextBody, JsonBody, FormBody, MultipartBody } from "@tripod311/currents"
* BinaryBody β dumps buffer into body
* TextBody β UTF-8 string body
* JsonBody β parses JSON
* FormBody β parses application/x-www-form-urlencoded
* MultipartBody β parses multipart bodies (only for tests, stores request in memory, can cause OOM errors and vulnerable to attacks)
* StreamingMultipartBody β streaming multipart/form-data parser, for real cases
---
`ts
import { Context, ParseCookies, SetCookie } from "@tripod311/currents"
app.get('/', [
ParseCookies(),
(ctx: Context) => {
ctx.cookies; // filled with cookies
SetCookie(ctx, 'myCookie', 'myValue', {
maxAge?: number,
expires?: Date,
httpOnly?: boolean,
secure?: boolean,
sameSite?: "Strict" | "Lax" | "None",
path?: string,
domain?: string
});
}
])
`
You can provide a secret string to ParseCookies if you want signed cookies.
---
`ts
import { ServeStatic } from "@tripod311/currents"
app.get('/*', [
ServeStatic({
basePath: '/',
rootDir: '/home/me/site',
cacheControl: ["public", "max-age=0"],
fallback: 'index.html'
})
])
`
* cacheControl and fallback are optional
* basePath defines which part of path should be cut before file lookup
* fallback is useful for SPA
---
`ts
import { Cors } from "@tripod311/currents"
app.get('/*', [
Cors({
allowedOrigin: string | string[],
allowedMethods?: string[],
allowedHeaders?: '*' | string[],
credentials?: boolean
})
])
`
Passing asterisk in allowedHeaders field will make cors reply with access-control-request-headers that browser requires.
---
`ts
import { SecurityHeaders } from "@tripod311/currents"
app.get('/*', [
SecurityHeaders({
contentTypeOptions?: boolean,
xFrameOptions?: 'DENY' | 'SAMEORIGIN',
referrerPolicy?: "no-referrer" | "no-referrer-when-downgrade" | "origin" | "origin-when-cross-origin" | "same-origin" | "strict-origin" | "strict-origin-when-cross-origin" | "unsafe-url",
transportSecurity?: {
maxAge: number,
includeSubDomains?: boolean,
preload?: boolean
},
contentSecurityPolicy?: string,
crossOriginResourcePolicy?: 'same-origin' | 'same-site' | 'cross-origin',
permissionsPolicy?: string
})
])
`
---
` ts
import { StreamingMultipartBody } from "@tripod311/currents"
import type { StreamingMultipartResult, StreamingMultipartFile, StreamingMultipartOptions } from "@tripod311/currents"
app.post('/*', [
StreamingMultipartBody({
tmpDir: "/path/to/folder/for/temp/files",
maxRequestSize: 1024 1024 150, // 150 MB
maxFileSize: 1024 1024 100, // 100 MB
maxFieldSize: 1024 1024 10, // 10 MB
maxPartHeaderSize: 1024 * 16, // 16 KB
maxParts: 50,
maxFiles: 10,
requestTimeout: 1000 60 5, // 5 min
chunkTimeout: 1000 * 30 // 30 sec
})
])
`
This middleware provides a **streaming, production-safe
multipart/form-data parser** designed for handling large file uploads
(up to \~100--200 MB) without loading entire files into memory.
It uses:
- a streaming state machine
- strict size limits
- request and chunk timeouts
- disk-backed file storage
- DoS protection (limits on parts, headers, files, etc.)
The values shown above are example limits, but they are reasonable
defaults for real-world API usage and can safely be used as a starting
point.
------------------------------------------------------------------------
#### Result structure
After successful parsing, ctx.body will contain:
` ts`
export type StreamingMultipartResult =
Record
Text fields are returned as strings.\
File fields are returned as StreamingMultipartFile instances:
` ts
export class StreamingMultipartFile {
public tmpLink: string;
public originalFileName: string;
public mime: string;
constructor (tmpLink: string, originalFileName: string, mime: string) {
this.tmpLink = tmpLink;
this.originalFileName = originalFileName;
this.mime = mime;
}
async move (newPath: string) {
await fs.promises.rename(this.tmpLink, newPath);
await this.clear();
}
async clear () {
await fs.promises.rm(this.tmpLink);
}
}
`
##### Example
` ts
app.post('/upload', async (ctx) => {
const body = ctx.body as StreamingMultipartResult;
const username = body.username as string;
const avatar = body.avatar as StreamingMultipartFile;
await avatar.move(/permanent/storage/${avatar.originalFileName});`
});
------------------------------------------------------------------------
#### β Temporary file lifecycle
Uploaded files are written to the temporary directory specified in
tmpDir.
You are responsible for either:
- moving the file to permanent storage using move(), orclear()`.
- manually deleting it using
The middleware **only removes temporary files automatically in case of
an error** (e.g.Β size limit exceeded, timeout, malformed multipart).
If the request completes successfully, file cleanup is your
responsibility.
------------------------------------------------------------------------
#### Why streaming?
This parser:
- never loads full files into memory
- processes request data chunk by chunk
- enforces strict limits to prevent OOM and slowloris attacks
- validates multipart boundaries safely across chunk splits
It is suitable for APIs that need controlled large-file handling without
relying on third-party multipart libraries.