bento-s3
v1.0.0
Published
A lightweight S3-compatible API server for local development, automated testing, and CI environments.
Downloads
86
Readme
BentoS3
BentoS3 is a lightweight, S3-compatible API server for local development, automated testing, and CI environments.
It is designed as both:
- A standalone CLI-bootable S3-compatible server.
- An embeddable Node.js library for Vitest, Jest, and framework-based test suites.
BentoS3 aims to provide the most commonly used S3 behavior without the operational weight of MinIO, LocalStack, or a full object-storage server.
BentoS3 is intended for local development, automated tests, and CI. It is not production object storage and does not attempt full S3 feature parity.
Installation
Install BentoS3 from npm:
npm install bento-s3Then import it in your project:
import { BentoS3, MemoryAuthStore } from "bento-s3";Goals
- Support a practical subset of the S3 HTTP API.
- Work with the official AWS SDK for JavaScript.
- Persist buckets and objects to the local filesystem.
- Provide a framework-neutral core that can be embedded in any Node.js HTTP stack.
- Provide adapters for common frameworks such as Express, Koa, Fastify, and Node HTTP.
- Include a lightweight server-rendered dashboard.
- Keep dependencies lean for fast installs in CI.
Non-Goals
- Production-grade object storage.
- Full S3 feature parity.
- Distributed storage.
- Replication, lifecycle policies, object lock, ACLs, or bucket policies.
Usage
Managed Test Server
import { BentoS3, MemoryAuthStore } from "bento-s3";
const authStore = new MemoryAuthStore();
await authStore.createCredential({
accessKeyId: "test",
secretAccessKey: "test-secret",
});
const s3 = new BentoS3({
port: 0,
authStore,
});
await s3.start();
console.log(s3.endpoint);
await s3.stop();AWS SDK Client
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
const client = new S3Client({
region: "us-east-1",
endpoint: s3.endpoint,
forcePathStyle: true,
credentials: {
accessKeyId: "test",
secretAccessKey: "test-secret",
},
});
await client.send(
new PutObjectCommand({
Bucket: "photos",
Key: "cat.jpg",
Body: Buffer.from("image-bytes"),
}),
);CLI
Start a local S3-compatible server with the dashboard enabled:
bentos3 serve
bentos3 serve --host 127.0.0.1 --port 9000 --root-dir ./.bentos3Create a dashboard user:
bentos3 user create admin
bentos3 user create admin --password mypasswordList dashboard users and buckets:
bentos3 user list
bentos3 bucket listDefault server behavior:
| Option | Default |
| ------------ | ------------ |
| --host | 127.0.0.1 |
| --port | 9000 |
| --root-dir | ./.bentos3 |
| Dashboard | Enabled |
| Auth store | JSON-backed |
On first start, bentos3 serve bootstraps a default access key and prints the credentials.
Framework-Embedded Server
import express from "express";
import { MemoryAuthStore } from "bento-s3";
import { BentoS3Core } from "bento-s3/core";
import { expressAdapter } from "bento-s3/adapters/express";
const app = express();
const authStore = new MemoryAuthStore();
await authStore.createCredential({
accessKeyId: "test",
secretAccessKey: "test-secret",
});
const bento = new BentoS3Core({ authStore });
app.use("/s3", expressAdapter(bento));The AWS SDK endpoint for this mounted route should include the mount path:
new S3Client({
endpoint: "http://127.0.0.1:3000/s3",
forcePathStyle: true,
region: "us-east-1",
credentials: {
accessKeyId: "test",
secretAccessKey: "test-secret",
},
});Framework Adapters
Express:
import { expressAdapter } from "bento-s3/adapters/express";
app.use("/s3", expressAdapter(bento));Koa:
import { koaAdapter } from "bento-s3/adapters/koa";
app.use(koaAdapter(bento, { basePath: "/s3" }));Fastify:
import { fastifyBentoS3 } from "bento-s3/adapters/fastify";
await app.register(fastifyBentoS3, {
prefix: "/s3",
bento,
});Fetch:
import { BentoS3Core } from "bento-s3/core";
import { handleFetchRequest } from "bento-s3/adapters/fetch";
const bento = new BentoS3Core({
auth: { enabled: false },
});
const response = await handleFetchRequest(bento, request, { basePath: "/s3" });The Fetch adapter passes requests through the configured BentoS3Core auth behavior. Use signed requests with a configured auth store, or disable auth for unsigned local test requests.
Body Parser Ordering
BentoS3 adapters must receive the raw request stream. Mount BentoS3 before body parsers for the S3 route:
app.use("/s3", expressAdapter(bento));
app.use(express.json());Avoid this ordering for S3 requests:
app.use(express.json());
app.use("/s3", expressAdapter(bento));The same rule applies to Koa middleware that reads ctx.req. Fastify registration installs a raw content-type parser for the plugin scope so S3 object uploads are not pre-parsed before BentoS3 receives them.
Core Design
BentoS3 is protocol-engine first. The S3 implementation is not coupled to Express, Koa, Fastify, or any other framework.
The core accepts an internal request object and returns an internal response object:
export interface BentoRequest {
method: string;
url: string;
path: string;
canonicalPath?: string;
query: URLSearchParams;
headers: Record<string, string | string[] | undefined>;
body?: AsyncIterable<Uint8Array> | NodeJS.ReadableStream;
remoteAddress?: string;
}
export interface BentoResponse {
statusCode: number;
headers: Record<string, string | number | string[]>;
body?: string | Uint8Array | AsyncIterable<Uint8Array> | NodeJS.ReadableStream;
}Adapters translate framework-specific request and response types into this contract.
S3 Compatibility
The compatibility target is the official AWS SDK for JavaScript v3 using path-style addressing:
new S3Client({
endpoint: "http://127.0.0.1:9000",
forcePathStyle: true,
region: "us-east-1",
credentials,
});Supported S3 operations:
ListBucketsCreateBucketDeleteBucketHeadBucketListObjectsV2PutObjectGetObjectHeadObjectDeleteObjectDeleteObjectsCopyObject
Current compatibility limits:
- Requests must use path-style addressing.
- SigV4 header authentication is supported; presigned URL query authentication is not supported.
ListObjectsV2supports prefix filtering, but not delimiter grouping, continuation tokens,StartAfter, or customMaxKeyspagination.- Multipart upload, range requests, ACLs, bucket policies, object tagging, lifecycle policies, replication, and object lock are not supported.
- Bucket names are restricted to filesystem-safe path segments for local persistence.
Storage
BentoS3 uses the local filesystem as its primary persistence layer. Storage drivers write a .bentos3/ data directory inside the configured rootDir.
Example layout:
.bentos3/
buckets/
photos/
.bentos3-bucket.json
cats/leo.jpg
cats/leo.jpg.meta.json
auth/
credentials.json
dashboard-users.json
sessions.json
tmp/Buckets map to directories. Objects map to files. Object metadata is stored in JSON sidecar files.
Auth
BentoS3 supports two credential store implementations:
MemoryAuthStore- in-memory, ideal for tests.JsonAuthStore- JSON-backed, persists credentials to disk for local development.
SigV4 authentication is enabled by default. Configure the auth store through BentoS3 or BentoS3Core options:
import { BentoS3, JsonAuthStore } from "bento-s3";
const rootDir = "./.bentos3";
const s3 = new BentoS3({
port: 9000,
rootDir,
authStore: new JsonAuthStore({ rootDir }),
});Auth can be disabled for testing:
import { BentoS3Core } from "bento-s3/core";
const bento = new BentoS3Core({
auth: { enabled: false },
});Request body buffering is capped by default to keep local runs from accidentally exhausting memory. Configure the limit when larger test fixtures are needed:
const s3 = new BentoS3({
maxRequestBodyBytes: 250 * 1024 * 1024,
});Dashboard
The dashboard is a server-rendered UI for managing buckets, objects, and access keys. It is enabled by default when running bentos3 serve or creating a BentoS3 instance without dashboard.enabled: false.
Access the dashboard at http://127.0.0.1:9000/ui.
Dashboard Features
- Browse and manage buckets and objects.
- Upload and download objects from the browser.
- Create and revoke S3 access keys.
- Session-based authentication with
HttpOnlycookies.
Dashboard Users
Create a dashboard user via the CLI:
bentos3 user create admin --password mypasswordDashboard passwords are hashed with Node crypto.scrypt. Sessions store token hashes, not raw tokens.
Technology
- EJS templates.
- Inline CSS styled with a compact dashboard design system.
- Turbo-compatible static script placeholder.
- JSON files for user and session storage.
Package Exports
| Export path | Contents |
| ----------------------------- | ----------------------------------------------------------------------- |
| bento-s3 | BentoS3, BentoS3Core, adapters, auth stores, storage drivers, types |
| bento-s3/core | BentoS3Core, BentoS3CoreOptions |
| bento-s3/adapters/node-http | Node HTTP adapter utilities |
| bento-s3/adapters/express | expressAdapter |
| bento-s3/adapters/koa | koaAdapter |
| bento-s3/adapters/fastify | fastifyBentoS3 |
| bento-s3/adapters/fetch | handleFetchRequest |
