upload.ts

_58
import type { APIRoute } from "astro";
_58
import { API } from "../../utils/api";
_58
_58
export const POST: APIRoute = async ({ request, locals }) => {
_58
// Set the origin for the API
_58
API.init(locals.runtime.env.ORIGIN);
_58
_58
// Handle CORS preflight requests
_58
if (request.method === "OPTIONS") {
_58
console.log("CORS preflight request from:", request.headers.get("Origin"));
_58
return API.cors(request);
_58
}
_58
_58
try {
_58
// Check if bucket is available
_58
const bucket = locals.runtime.env.CLOUD_FILES;
_58
if (!bucket) {
_58
return API.error("Cloud storage not configured", request, 500);
_58
}
_58
_58
const formData = await request.formData();
_58
const file = formData.get("file");
_58
_58
if (!file || !(file instanceof File)) {
_58
return API.error("Missing or invalid file", request, 400);
_58
}
_58
_58
// Generate unique filename with timestamp
_58
const timestamp = Date.now();
_58
const extension = file.name.split(".").pop() || "";
_58
const filename = `${timestamp}-${file.name}`;
_58
_58
// Upload to R2 bucket
_58
const object = await bucket.put(filename, file, {
_58
httpMetadata: {
_58
contentType: file.type,
_58
},
_58
});
_58
_58
if (!object) {
_58
return API.error("Failed to upload file", request, 500);
_58
}
_58
_58
return API.success(
_58
{
_58
success: true,
_58
filename,
_58
key: object.key,
_58
size: file.size,
_58
type: file.type,
_58
},
_58
request
_58
);
_58
} catch (error) {
_58
console.error("Upload error:", error);
_58
return API.error("Upload failed", request, 500);
_58
}
_58
};

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.

upload.ts

_58
import type { APIRoute } from "astro";
_58
import { API } from "../../utils/api";
_58
_58
export const POST: APIRoute = async ({ request, locals }) => {
_58
// Set the origin for the API
_58
API.init(locals.runtime.env.ORIGIN);
_58
_58
// Handle CORS preflight requests
_58
if (request.method === "OPTIONS") {
_58
console.log("CORS preflight request from:", request.headers.get("Origin"));
_58
return API.cors(request);
_58
}
_58
_58
try {
_58
// Check if bucket is available
_58
const bucket = locals.runtime.env.CLOUD_FILES;
_58
if (!bucket) {
_58
return API.error("Cloud storage not configured", request, 500);
_58
}
_58
_58
const formData = await request.formData();
_58
const file = formData.get("file");
_58
_58
if (!file || !(file instanceof File)) {
_58
return API.error("Missing or invalid file", request, 400);
_58
}
_58
_58
// Generate unique filename with timestamp
_58
const timestamp = Date.now();
_58
const extension = file.name.split(".").pop() || "";
_58
const filename = `${timestamp}-${file.name}`;
_58
_58
// Upload to R2 bucket
_58
const object = await bucket.put(filename, file, {
_58
httpMetadata: {
_58
contentType: file.type,
_58
},
_58
});
_58
_58
if (!object) {
_58
return API.error("Failed to upload file", request, 500);
_58
}
_58
_58
return API.success(
_58
{
_58
success: true,
_58
filename,
_58
key: object.key,
_58
size: file.size,
_58
type: file.type,
_58
},
_58
request
_58
);
_58
} catch (error) {
_58
console.error("Upload error:", error);
_58
return API.error("Upload failed", request, 500);
_58
}
_58
};

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.

upload.ts

_58
import type { APIRoute } from "astro";
_58
import { API } from "../../utils/api";
_58
_58
export const POST: APIRoute = async ({ request, locals }) => {
_58
// Set the origin for the API
_58
API.init(locals.runtime.env.ORIGIN);
_58
_58
// Handle CORS preflight requests
_58
if (request.method === "OPTIONS") {
_58
console.log("CORS preflight request from:", request.headers.get("Origin"));
_58
return API.cors(request);
_58
}
_58
_58
try {
_58
// Check if bucket is available
_58
const bucket = locals.runtime.env.CLOUD_FILES;
_58
if (!bucket) {
_58
return API.error("Cloud storage not configured", request, 500);
_58
}
_58
_58
const formData = await request.formData();
_58
const file = formData.get("file");
_58
_58
if (!file || !(file instanceof File)) {
_58
return API.error("Missing or invalid file", request, 400);
_58
}
_58
_58
// Generate unique filename with timestamp
_58
const timestamp = Date.now();
_58
const extension = file.name.split(".").pop() || "";
_58
const filename = `${timestamp}-${file.name}`;
_58
_58
// Upload to R2 bucket
_58
const object = await bucket.put(filename, file, {
_58
httpMetadata: {
_58
contentType: file.type,
_58
},
_58
});
_58
_58
if (!object) {
_58
return API.error("Failed to upload file", request, 500);
_58
}
_58
_58
return API.success(
_58
{
_58
success: true,
_58
filename,
_58
key: object.key,
_58
size: file.size,
_58
type: file.type,
_58
},
_58
request
_58
);
_58
} catch (error) {
_58
console.error("Upload error:", error);
_58
return API.error("Upload failed", request, 500);
_58
}
_58
};

Process form data and validate file

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

upload.ts

_58
import type { APIRoute } from "astro";
_58
import { API } from "../../utils/api";
_58
_58
export const POST: APIRoute = async ({ request, locals }) => {
_58
// Set the origin for the API
_58
API.init(locals.runtime.env.ORIGIN);
_58
_58
// Handle CORS preflight requests
_58
if (request.method === "OPTIONS") {
_58
console.log("CORS preflight request from:", request.headers.get("Origin"));
_58
return API.cors(request);
_58
}
_58
_58
try {
_58
// Check if bucket is available
_58
const bucket = locals.runtime.env.CLOUD_FILES;
_58
if (!bucket) {
_58
return API.error("Cloud storage not configured", request, 500);
_58
}
_58
_58
const formData = await request.formData();
_58
const file = formData.get("file");
_58
_58
if (!file || !(file instanceof File)) {
_58
return API.error("Missing or invalid file", request, 400);
_58
}
_58
_58
// Generate unique filename with timestamp
_58
const timestamp = Date.now();
_58
const extension = file.name.split(".").pop() || "";
_58
const filename = `${timestamp}-${file.name}`;
_58
_58
// Upload to R2 bucket
_58
const object = await bucket.put(filename, file, {
_58
httpMetadata: {
_58
contentType: file.type,
_58
},
_58
});
_58
_58
if (!object) {
_58
return API.error("Failed to upload file", request, 500);
_58
}
_58
_58
return API.success(
_58
{
_58
success: true,
_58
filename,
_58
key: object.key,
_58
size: file.size,
_58
type: file.type,
_58
},
_58
request
_58
);
_58
} catch (error) {
_58
console.error("Upload error:", error);
_58
return API.error("Upload failed", request, 500);
_58
}
_58
};

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

_58
import type { APIRoute } from "astro";
_58
import { API } from "../../utils/api";
_58
_58
export const POST: APIRoute = async ({ request, locals }) => {
_58
// Set the origin for the API
_58
API.init(locals.runtime.env.ORIGIN);
_58
_58
// Handle CORS preflight requests
_58
if (request.method === "OPTIONS") {
_58
console.log("CORS preflight request from:", request.headers.get("Origin"));
_58
return API.cors(request);
_58
}
_58
_58
try {
_58
// Check if bucket is available
_58
const bucket = locals.runtime.env.CLOUD_FILES;
_58
if (!bucket) {
_58
return API.error("Cloud storage not configured", request, 500);
_58
}
_58
_58
const formData = await request.formData();
_58
const file = formData.get("file");
_58
_58
if (!file || !(file instanceof File)) {
_58
return API.error("Missing or invalid file", request, 400);
_58
}
_58
_58
// Generate unique filename with timestamp
_58
const timestamp = Date.now();
_58
const extension = file.name.split(".").pop() || "";
_58
const filename = `${timestamp}-${file.name}`;
_58
_58
// Upload to R2 bucket
_58
const object = await bucket.put(filename, file, {
_58
httpMetadata: {
_58
contentType: file.type,
_58
},
_58
});
_58
_58
if (!object) {
_58
return API.error("Failed to upload file", request, 500);
_58
}
_58
_58
return API.success(
_58
{
_58
success: true,
_58
filename,
_58
key: object.key,
_58
size: file.size,
_58
type: file.type,
_58
},
_58
request
_58
);
_58
} catch (error) {
_58
console.error("Upload error:", error);
_58
return API.error("Upload failed", request, 500);
_58
}
_58
};

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.

upload.ts

_58
import type { APIRoute } from "astro";
_58
import { API } from "../../utils/api";
_58
_58
export const POST: APIRoute = async ({ request, locals }) => {
_58
// Set the origin for the API
_58
API.init(locals.runtime.env.ORIGIN);
_58
_58
// Handle CORS preflight requests
_58
if (request.method === "OPTIONS") {
_58
console.log("CORS preflight request from:", request.headers.get("Origin"));
_58
return API.cors(request);
_58
}
_58
_58
try {
_58
// Check if bucket is available
_58
const bucket = locals.runtime.env.CLOUD_FILES;
_58
if (!bucket) {
_58
return API.error("Cloud storage not configured", request, 500);
_58
}
_58
_58
const formData = await request.formData();
_58
const file = formData.get("file");
_58
_58
if (!file || !(file instanceof File)) {
_58
return API.error("Missing or invalid file", request, 400);
_58
}
_58
_58
// Generate unique filename with timestamp
_58
const timestamp = Date.now();
_58
const extension = file.name.split(".").pop() || "";
_58
const filename = `${timestamp}-${file.name}`;
_58
_58
// Upload to R2 bucket
_58
const object = await bucket.put(filename, file, {
_58
httpMetadata: {
_58
contentType: file.type,
_58
},
_58
});
_58
_58
if (!object) {
_58
return API.error("Failed to upload file", request, 500);
_58
}
_58
_58
return API.success(
_58
{
_58
success: true,
_58
filename,
_58
key: object.key,
_58
size: file.size,
_58
type: file.type,
_58
},
_58
request
_58
);
_58
} catch (error) {
_58
console.error("Upload error:", error);
_58
return API.error("Upload failed", request, 500);
_58
}
_58
};

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

_58
import type { APIRoute } from "astro";
_58
import { API } from "../../utils/api";
_58
_58
export const POST: APIRoute = async ({ request, locals }) => {
_58
// Set the origin for the API
_58
API.init(locals.runtime.env.ORIGIN);
_58
_58
// Handle CORS preflight requests
_58
if (request.method === "OPTIONS") {
_58
console.log("CORS preflight request from:", request.headers.get("Origin"));
_58
return API.cors(request);
_58
}
_58
_58
try {
_58
// Check if bucket is available
_58
const bucket = locals.runtime.env.CLOUD_FILES;
_58
if (!bucket) {
_58
return API.error("Cloud storage not configured", request, 500);
_58
}
_58
_58
const formData = await request.formData();
_58
const file = formData.get("file");
_58
_58
if (!file || !(file instanceof File)) {
_58
return API.error("Missing or invalid file", request, 400);
_58
}
_58
_58
// Generate unique filename with timestamp
_58
const timestamp = Date.now();
_58
const extension = file.name.split(".").pop() || "";
_58
const filename = `${timestamp}-${file.name}`;
_58
_58
// Upload to R2 bucket
_58
const object = await bucket.put(filename, file, {
_58
httpMetadata: {
_58
contentType: file.type,
_58
},
_58
});
_58
_58
if (!object) {
_58
return API.error("Failed to upload file", request, 500);
_58
}
_58
_58
return API.success(
_58
{
_58
success: true,
_58
filename,
_58
key: object.key,
_58
size: file.size,
_58
type: file.type,
_58
},
_58
request
_58
);
_58
} catch (error) {
_58
console.error("Upload error:", error);
_58
return API.error("Upload failed", request, 500);
_58
}
_58
};

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

_58
import type { APIRoute } from "astro";
_58
import { API } from "../../utils/api";
_58
_58
export const POST: APIRoute = async ({ request, locals }) => {
_58
// Set the origin for the API
_58
API.init(locals.runtime.env.ORIGIN);
_58
_58
// Handle CORS preflight requests
_58
if (request.method === "OPTIONS") {
_58
console.log("CORS preflight request from:", request.headers.get("Origin"));
_58
return API.cors(request);
_58
}
_58
_58
try {
_58
// Check if bucket is available
_58
const bucket = locals.runtime.env.CLOUD_FILES;
_58
if (!bucket) {
_58
return API.error("Cloud storage not configured", request, 500);
_58
}
_58
_58
const formData = await request.formData();
_58
const file = formData.get("file");
_58
_58
if (!file || !(file instanceof File)) {
_58
return API.error("Missing or invalid file", request, 400);
_58
}
_58
_58
// Generate unique filename with timestamp
_58
const timestamp = Date.now();
_58
const extension = file.name.split(".").pop() || "";
_58
const filename = `${timestamp}-${file.name}`;
_58
_58
// Upload to R2 bucket
_58
const object = await bucket.put(filename, file, {
_58
httpMetadata: {
_58
contentType: file.type,
_58
},
_58
});
_58
_58
if (!object) {
_58
return API.error("Failed to upload file", request, 500);
_58
}
_58
_58
return API.success(
_58
{
_58
success: true,
_58
filename,
_58
key: object.key,
_58
size: file.size,
_58
type: file.type,
_58
},
_58
request
_58
);
_58
} catch (error) {
_58
console.error("Upload error:", error);
_58
return API.error("Upload failed", request, 500);
_58
}
_58
};