Back to Blog
Lesson 22 of the Intermediate Laravel: Real-World Application Patterns course
LaravelJune 26, 20263 min read

Handling File Uploads in REST APIs: A Laravel Guide

Master secure file uploads in Laravel REST APIs. Learn to validate binary data, use the Filesystem abstraction, and associate files with your Eloquent models.

LaravelREST APIStorageFilesystemValidationphpbackend

Previously in this course, we explored Handling API Validation and Form Requests. While that lesson focused on standard request payloads, today we’re moving into binary data. We’ll cover how to securely accept, store, and link files to your project board entities.

From First Principles: The Filesystem Abstraction

In a production environment, you should never hardcode file paths or manipulate the local disk directly. Laravel provides the Illuminate\Support\Facades\Storage facade, which acts as a filesystem abstraction layer. Whether you are storing files on your local server, an S3 bucket, or an SFTP server, your code remains identical.

When building a REST API, files arrive as multipart/form-data. Because these requests are inherently larger and potentially malicious, we treat them with higher scrutiny than standard JSON payloads.

Validating and Storing Files

Before storing a file, validation is non-negotiable. You must enforce constraints on file size and MIME types to prevent users from uploading executable scripts or exhausting your server’s disk space.

Building upon our project board, let’s add a feature to attach files to tasks. First, create a StoreTaskAttachmentRequest:

PHP
#6A9955">// app/Http/Requests/StoreTaskAttachmentRequest.php
public function rules(): array
{
    return [
        'attachment' => [
            'required', 
            'file', 
            'mimes:jpg,png,pdf', 
            'max:2048' #6A9955">// 2MB limit
        ],
    ];
}

Once validated, we use the store method. This method handles filename hashing (to prevent collisions) and returns the relative path to the file.

PHP
#6A9955">// Inside your TaskService
public function addAttachment(Task $task, UploadedFile $file): Attachment
{
    #6A9955">// Store in the 'attachments' directory on the 'public' disk
    $path = $file->store('attachments', 'public');

    return $task->attachments()->create([
        'path' => $path,
        'original_name' => $file->getClientOriginalName(),
        'mime_type' => $file->getClientMimeType(),
    ]);
}

Associating Files with Models

In a relational database, you shouldn't store the file itself in a BLOB column. Instead, store the metadata (path, original name, size) in a dedicated attachments table.

  1. The Migration: Create a migration with task_id, path, and original_name.
  2. The Relationship: Define a hasMany relationship on your Task model.
  3. The Storage: Use the public disk if the files need to be accessible via URL, or the local disk for private, server-only files.

Hands-on Exercise: Secure Attachment Upload

  1. Create a migration for an attachments table with a task_id foreign key and a path string column.
  2. Generate a TaskAttachmentController and inject your TaskService.
  3. Implement an endpoint POST /tasks/{task}/attachments.
  4. Use the store method to save the file and persist the metadata to the database.
  5. Verify that the file is stored in storage/app/public/attachments.

Common Pitfalls

  • Ignoring File Permissions: Ensure your storage/ directory is writable by the web server (usually www-data).
  • Trusting Client-Provided Names: Never use the filename provided by the user directly in your database or filesystem. Laravel's store() method automatically generates a unique hash, which is exactly what you want to prevent directory traversal attacks.
  • Hardcoding Local Paths: Always use Storage::url($path) to generate links. If you switch to S3 later, this method will automatically generate the correct signed URL without you changing a single line of business logic.
  • Memory Exhaustion: If your users upload massive files, ensure your php.ini (upload_max_filesize and post_max_size) matches your validation rules.

Recap

We’ve moved from simple JSON requests to handling binary data securely. By utilizing Laravel’s Storage facade, we've decoupled our application from the underlying storage mechanism. We ensure security through strict FormRequest validation and maintain database integrity by storing metadata rather than raw binary data.

Up next: We will implement Job Chaining and Batching to handle post-processing (like image resizing) for these file uploads in the background.

Similar Posts