Getting started
This guide walks you through adding Storage capabilities to your Layeron backend application. You will set up an R2-backed Bucket to store files, write a KV namespace for fast configurations, and handle JSON parsing in route handlers.
1. Set Up Object Storage (Bucket)
Section titled “1. Set Up Object Storage (Bucket)”Use storage.bucket when your application needs to handle larger files, media assets, uploads, or exports.
Declare and Register the Bucket
Section titled “Declare and Register the Bucket”Import storage from @layeron/modules and declare the bucket with a stable name. Register it using app.use().
import { backend } from "@layeron/core"import { storage } from "@layeron/modules"
const app = backend()
// Declare a private R2 object storage bucketconst assetsBucket = storage.bucket({ name: "user-assets", access: "private",})
app.use(assetsBucket)Upload Files (Write)
Section titled “Upload Files (Write)”Use the bucket.put() operation to save a file. put accepts strings, Uint8Arrays, ArrayBuffers, or Blobs. You can attach user-defined metadata and a contentType:
app.post("/upload/:filename", async (request) => { const pathSegments = new URL(request.url).pathname.split("/") const filename = pathSegments[2] const fileBuffer = await request.arrayBuffer()
const result = await assetsBucket.put(filename, fileBuffer, { contentType: request.headers.get("content-type") ?? "application/octet-stream", metadata: { uploadedBy: "system-client", }, })
return Response.json({ status: "uploaded", key: result.key, bytes: result.size, etag: result.etag, })})Use ifNotExists: true for create-only writes. When the key already exists, the write fails with storage_key_already_exists and HTTP status 409. Use checksum to store a caller-provided checksum in public object metadata:
await assetsBucket.put("avatars/user_123.png", fileBuffer, { contentType: "image/png", checksum: "sha256:abc123", ifNotExists: true,})
const uploaded = await assetsBucket.head("avatars/user_123.png")console.log(uploaded?.metadata.checksum)Download Files (Read)
Section titled “Download Files (Read)”Use bucket.get() to download a file. If the file exists, get() returns a StorageReadHandle that lets you read the data using .bytes(), .text(), or .json(). If the file does not exist, it returns null:
app.get("/download/:filename", async (request) => { const pathSegments = new URL(request.url).pathname.split("/") const filename = pathSegments[2] const fileHandle = await assetsBucket.get(filename)
if (!fileHandle) { return new Response("File not found", { status: 404 }) }
// Retrieve raw bytes from the read handle const rawBytes = await fileHandle.bytes()
return new Response(rawBytes, { headers: { "content-type": fileHandle.contentType ?? "application/octet-stream", }, })})2. Set Up Key-Value Storage (KV)
Section titled “2. Set Up Key-Value Storage (KV)”Use storage.kv when you need to store smaller, highly-cached, key-addressed properties like feature flags, configurations, or session values.
Declare and Register KV
Section titled “Declare and Register KV”Declare the KV namespace inside your application. You can set a global ttlSeconds to automatically expire key values:
// Declare a KV namespace with 24-hour default automatic expirationconst configStore = storage.kv({ name: "app-configs", ttlSeconds: 86400,})
app.use(configStore)Save and Read JSON (KV Operations)
Section titled “Save and Read JSON (KV Operations)”KV is highly optimized for fast JSON document reads. Write data using .put() and parse it directly inside your routes using .json():
// Write JSON configapp.post("/config/:key", async (request) => { const pathSegments = new URL(request.url).pathname.split("/") const key = pathSegments[2] const body = await request.json()
await configStore.put(key, JSON.stringify(body), { contentType: "application/json", })
return { success: true }})
// Read and parse JSON configapp.get("/config/:key", async (request) => { const pathSegments = new URL(request.url).pathname.split("/") const key = pathSegments[2] const handle = await configStore.get(key)
if (!handle) { return new Response("Config key not found", { status: 404 }) }
// Parse JSON data directly from the read handle const parsedData = await handle.json()
return Response.json(parsedData)})Next Steps
Section titled “Next Steps”- Guarantees and limits: Understand R2 consistency, KV propagation, object sizes, lifecycles, encryption, signed URLs, and custom hosts.
- Pre-signed uploads: Give clients short-lived upload URLs for private bucket writes.
- Encrypted vault: Store confidential objects with managed encryption and secret rotation.
- Paginated listing: Build a listing route and client-side infinite scroll.
- API reference: Review bucket, KV, encryption, lifecycle, signed URL, and read handle contracts.