Skip to content

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.


Use storage.bucket when your application needs to handle larger files, media assets, uploads, or exports.

Import storage from @layeron/modules and declare the bucket with a stable name. Register it using app.use().

Terminal window
import { backend } from "@layeron/core"
import { storage } from "@layeron/modules"
const app = backend()
// Declare a private R2 object storage bucket
const assetsBucket = storage.bucket({
name: "user-assets",
access: "private",
})
app.use(assetsBucket)

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:

Terminal window
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:

Terminal window
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)

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:

Terminal window
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",
},
})
})

Use storage.kv when you need to store smaller, highly-cached, key-addressed properties like feature flags, configurations, or session values.

Declare the KV namespace inside your application. You can set a global ttlSeconds to automatically expire key values:

Terminal window
// Declare a KV namespace with 24-hour default automatic expiration
const configStore = storage.kv({
name: "app-configs",
ttlSeconds: 86400,
})
app.use(configStore)

KV is highly optimized for fast JSON document reads. Write data using .put() and parse it directly inside your routes using .json():

Terminal window
// Write JSON config
app.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 config
app.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)
})

  • 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.