Create the POST endpoint

Import the APIRoute type from "astro" and create a POST endpoint that accepts the locals parameter. This endpoint will handle file uploads to the R2 bucket, including CORS preflight requests and form data processing.

Initialize API and handle CORS

Initialize the API utility with the origin set in your environment variables and handle CORS preflight requests. The OPTIONS method is used by browsers to check if the cross-origin request is allowed before sending the actual file data.

Key concepts:

  • CORS preflight: Browsers send OPTIONS requests to check cross-origin permissions
  • API initialization: Set up the API utility with the correct origin for CORS headers

Validate bucket configuration

Access the CLOUD_FILES binding and verify that the Object Storage bucket is properly configured. If the bucket isn't available, return a 500 error with a descriptive message to help with debugging.

Process form data and validate file

Extract the file from the FormData object and validate that it's a proper File instance.

Generate unique filename

Create a unique filename by combining a timestamp with the original filename. This prevents filename collisions and ensures each upload has a unique identifier. Extract the file extension to preserve the file type.

Upload file to the bucket

Use the .put() method to upload the file to the Object Storage bucket with the generated filename. Set the httpMetadata to include the file's content type, which is essential for proper file serving later.

Return success response

Return a success response with file details including the generated filename, Object Storage key, file size, and content type. This information is useful for the client to track the uploaded file.

upload.ts
ExpandClose
1
import type { APIRoute } from "astro";
2
import { API } from "../../utils/api";
3

4
export const POST: APIRoute = async ({ request, locals }) => {
5
// Set the origin for the API
6
API.init(locals.runtime.env.ORIGIN);
7

8
// Handle CORS preflight requests
9
if (request.method === "OPTIONS") {
10
console.log("CORS preflight request from:", request.headers.get("Origin"));
11
return API.cors(request);
12
}
13

14
try {
15
// Check if bucket is available
16
const bucket = locals.runtime.env.CLOUD_FILES;
17
if (!bucket) {
18
return API.error("Cloud storage not configured", request, 500);
19
}
20

21
const formData = await request.formData();
22
const file = formData.get("file");
23

24
if (!file || !(file instanceof File)) {
25
return API.error("Missing or invalid file", request, 400);
26
}
27

28
// Generate unique filename with timestamp
29
const timestamp = Date.now();
30
const extension = file.name.split(".").pop() || "";
31
const filename = `${timestamp}-${file.name}`;
32

33
// Upload to R2 bucket
34
const object = await bucket.put(filename, file, {
35
httpMetadata: {
36
contentType: file.type,
37
},
38
});
39

40
if (!object) {
41
return API.error("Failed to upload file", request, 500);
42
}
43

44
return API.success(
45
{
46
success: true,
47
filename,
48
key: object.key,
49
size: file.size,
50
type: file.type,
51
},
52
request
53
);
54
} catch (error) {
55
console.error("Upload error:", error);
56
return API.error("Upload failed", request, 500);
57
}
58
};