Backend Client
Interact with EdgeStore from your backend.
Sometimes you might want to use the EdgeStore functionality directly from your backend. Things like deleting, uploading or even listing files can be done with the use of the backend client.
Setup
The router exposes a type-safe backend client through router.client. The HTTP
handler and backend client share the same provider. EdgeStore is the default;
chain .provider(myProvider) on the router to use a different provider.
Since Next.js doesn't allow exports in the api route, you will need to move your router to an external file.
import { initEdgeStore } from '@edgestore/server';
import { createEdgeStoreNextHandler } from '@edgestore/server/adapters/next/app';
const es = initEdgeStore.create();
export const router = es.router({
publicFiles: es.fileBucket(),
});
export const handler = createEdgeStoreNextHandler({
router,
});
export const backendClient = router.client;Then you will need to update your api route to use the exported handler.
import { handler } from '@/lib/edgestore-server';
export { handler as GET, handler as POST };You can find an example of the backend client usage in the next-advanced example.
The backend client is privileged. It validates router input and applies file
type, size, transform, path, and metadata rules, but it does not run
accessControl, beforeUpload, or beforeDelete. Perform authorization in
the server code that calls it.
The hosted edgestore() provider supports the complete backend client. S3 and
Azure Blob Storage support upload, get, delete, deleteMany, and
private-read createSignedUrl / createSignedUrls, accepting key or URL
references. Unsupported operations, such as listing and confirmation, remain
absent from each provider's client type.
Backend Upload
You can use the upload function to upload files from your backend.
Upload a text file
The simplest use case would be to just upload a txt file:
const res = await backendClient.publicFiles.upload({
content: 'some text content',
});Upload a blob
You can also upload a more complex file using the Blob object. And there are also all the other options available in the normal upload.
const res = await backendClient.publicFiles.upload({
content: {
blob: new Blob(['col1,col2,col2'], { type: 'text/csv' }),
extension: 'csv',
},
options: {
temporary: true,
},
ctx: {
userId: '123',
userRole: 'admin',
},
input: {
type: 'post',
},
signal,
onProgress: ({ percentage, phase }) => {
console.log(phase, `${percentage}%`);
},
});
console.log(res.id, res.key, res.sizeBytes);Copy an existing file
You can use an existing file's URL to copy it into the EdgeStore bucket. This can be an external file (from outside of EdgeStore) or an existing EdgeStore file.
const res = await backendClient.publicFiles.upload({
content: {
url: 'https://some-url.com/file.txt',
extension: 'txt',
},
});Transform a file before upload
You can transform backend uploads before EdgeStore validates and uploads them.
The transform receives the resolved Blob and extension, and returns the new
Blob and extension.
For example, you can use sharp to convert an image to WebP before upload:
npm install sharpimport sharp from 'sharp';
const res = await backendClient.publicImages.upload({
content: {
url: 'https://some-url.com/image.jpg',
extension: 'jpg',
},
options: {
transform: async ({ blob }) => {
const input = Buffer.from(await blob.arrayBuffer());
const output = await sharp(input).webp({ quality: 80 }).toBuffer();
return {
blob: new Blob([output], { type: 'image/webp' }),
extension: 'webp',
};
},
},
});Confirm a temporary file upload
Save the upload result's ID/key and URL in your database, then confirm the
temporary file. Hosted EdgeStore accepts confirmation during processing; a
preceding get() is unnecessary and can return 404 until processing finishes.
If saving fails, leave the file temporary.
const res = await backendClient.publicFiles.confirm({
id: file.id,
});File operations accept a stable file ID, storage key, or URL. Singular
operations throw EdgeStoreFileMutationError when that file fails. Use the
plural form when partial success should be preserved:
const result = await backendClient.publicFiles.confirmMany({
refs: [{ id: first.id }, { key: second.key }],
});
for (const failure of result.failed) {
console.error(failure.ref, failure.error.code);
}Backend Delete
You can use the delete function to delete files from your backend.
const res = await backendClient.publicFiles.delete({
id: file.id,
});deleteMany, restore, and restoreMany use the same singular and
partial-batch semantics.
Backend List Files (search)
You can use the list function to list files from your backend. It's also possible to filter the results by path, metadata or upload timing.
// simple usage
// get the first 20 files in the bucket
const res = await backendClient.publicFiles.list();
// with filter and pagination
const res = await backendClient.publicFiles.list({
filter: {
metadata: {
role: 'admin',
},
path: {
type: 'post',
},
uploadedAt: {
gt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 7), // past 7 days
},
},
cursor: 'cursor-from-previous-response',
limit: 50, // default: 20 (max: 100)
});
for (const file of res.items) {
console.log(file.id, file.url);
}
if (res.hasMore) {
console.log('Next cursor:', res.nextCursor);
}Private read URLs
Providers with signed-read support expose createSignedUrl on protected
buckets:
const access = await backendClient.privateFiles.createSignedUrl({
url: { id: file.id },
expiresIn: 15 * 60,
});
console.log(access.signedUrl, access.expiresAt);Use createSignedUrls to sign several references in one provider call.
Reusing backend client types
Derive a single method's exact input or output directly from the configured client:
type UploadInput = Parameters<
typeof router.client.publicFiles.upload
>[0];
type UploadOutput = Awaited<
ReturnType<typeof router.client.publicFiles.upload>
>;This includes the router's context, input, path, and metadata configuration and the provider's file and reference types.
Use InferClientInputs and InferClientOutputs when you need a reusable map of
every bucket and method:
import type {
InferClientInputs,
InferClientOutputs,
} from '@edgestore/server';
export type BackendInputs = InferClientInputs<typeof router>;
export type BackendOutputs = InferClientOutputs<typeof router>;
type UploadInput = BackendInputs['publicFiles']['upload'];
type UploadOutput = BackendOutputs['publicFiles']['upload'];The helpers infer capabilities and provider-specific types directly from the
router, including custom providers. Use EdgeStoreClient<typeof router> for
the complete backend client type.