Overview
The Files API provides secure file upload functionality using AWS S3 pre-signed URLs. This allows clients to upload files directly to S3 without exposing AWS credentials.
This endpoint requires authentication. Only logged-in users can generate upload URLs.
Environment Configuration
The file router requires the following environment variables (from .env.example):
AWS_ACCESS_KEY_ID = "your-aws-access-key"
AWS_SECRET_ACCESS_KEY = "your-aws-secret-key"
AWS_REGION = "your-aws-region"
AWS_BUCKET_NAME = "your-bucket-name"
Mutations
Generate Upload URL
Generate a pre-signed S3 URL for direct file upload from the client.
Input Parameters
Name of the file to upload (e.g., “profile-picture.jpg”)
MIME type of the file (e.g., “image/jpeg”, “application/pdf”)
const { url , key } = await trpc . file . generateUrl . mutate ({
filename: "portfolio-image.jpg" ,
filetype: "image/jpeg"
});
Response
Pre-signed S3 URL valid for 120 seconds
S3 object key (format: {uuid}/{filename})
{
"url" : "https://your-bucket.s3.amazonaws.com/a1b2c3d4-e5f6-7890-abcd-ef1234567890/portfolio-image.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=..." ,
"key" : "a1b2c3d4-e5f6-7890-abcd-ef1234567890/portfolio-image.jpg"
}
Implementation Details
From ~/workspace/source/src/server/api/routers/file.ts:
import S3 from "aws-sdk/clients/s3" ;
import { randomUUID } from "crypto" ;
const s3 = new S3 ({
accessKeyId: env . AWS_ACCESS_KEY_ID ,
secretAccessKey: env . AWS_SECRET_ACCESS_KEY ,
region: env . AWS_REGION ,
signatureVersion: "v4" ,
});
export const fileRouter = createTRPCRouter ({
generateUrl: protectedProcedure
. input ( z . object ({ filename: z . string (), filetype: z . string () }))
. mutation ( async ({ input }) => {
const { filename , filetype } = input ;
const fileId = randomUUID ();
const key = ` ${ fileId } / ${ filename } ` ;
const params = {
Bucket: env . AWS_BUCKET_NAME ,
Key: key ,
Expires: 120 , // URL valid for 2 minutes
ContentType: filetype ,
};
const url = s3 . getSignedUrl ( "putObject" , params );
return { url , key };
}),
});
Upload Flow
Request upload URL
Call file.generateUrl with filename and file type const { url , key } = await trpc . file . generateUrl . mutate ({
filename: file . name ,
filetype: file . type
});
Upload file to S3
Use the pre-signed URL to upload directly to S3 await fetch ( url , {
method: "PUT" ,
body: file ,
headers: {
"Content-Type" : file . type ,
},
});
Construct public URL
Build the public URL using the returned key const publicUrl = `https:// ${ env . AWS_BUCKET_NAME } .s3. ${ env . AWS_REGION } .amazonaws.com/ ${ key } ` ;
Save URL to database
Store the public URL in your database (e.g., as attachment, profile picture, or gig image) await trpc . user . updateUserPersonalInfo . mutate ({
profilePic: publicUrl
});
Complete Upload Example
import { api } from "@/trpc/react" ;
async function uploadFile ( file : File ) {
try {
// Step 1: Get pre-signed URL
const { url , key } = await api . file . generateUrl . mutate ({
filename: file . name ,
filetype: file . type ,
});
// Step 2: Upload to S3
const uploadResponse = await fetch ( url , {
method: "PUT" ,
body: file ,
headers: {
"Content-Type" : file . type ,
},
});
if ( ! uploadResponse . ok ) {
throw new Error ( "Upload failed" );
}
// Step 3: Construct public URL
const publicUrl = `https:// ${ process . env . NEXT_PUBLIC_AWS_BUCKET_NAME } .s3. ${ process . env . NEXT_PUBLIC_AWS_REGION } .amazonaws.com/ ${ key } ` ;
// Step 4: Return public URL for database storage
return publicUrl ;
} catch ( error ) {
console . error ( "File upload error:" , error );
throw error ;
}
}
Usage Contexts
This API is used throughout Khedma Market for:
Profile Pictures - User avatar uploads
Gig Galleries - Service showcase images
Portfolio Projects - Images, videos, and documents
Message Attachments - File sharing in conversations
Company Logos - Brand image uploads
Security Features
Authentication Required - Only authenticated users can generate URLs
Time-Limited URLs - Pre-signed URLs expire after 120 seconds
Unique Keys - Each upload gets a UUID-based unique path
Content-Type Validation - File type is specified and enforced
Error Handling
try {
const { url , key } = await trpc . file . generateUrl . mutate ({
filename: file . name ,
filetype: file . type ,
});
} catch ( error ) {
if ( error . code === "UNAUTHORIZED" ) {
// User is not logged in
} else {
// Other error (network, S3 configuration, etc.)
}
}
Related Pages