From 400-Line Import Controllers to 20-Line Configs in Laravel

Published: (February 4, 2026 at 05:35 PM EST)
4 min read
Source: Dev.to

Source: Dev.to

Cover image for From 400-Line Import Controllers to 20-Line Configs in Laravel

Robin

The “Import Nightmares” We All Know

If you’ve built business applications with Laravel, you’ve definitely received this ticket:

“As an admin, I need to upload a CSV with 50,000 products to update our stock levels.”

I’ve seen this request at least a dozen times across different projects. It sounds simple, so you grab a CSV parser—maybe league/csv or maatwebsite/excel—and start writing a controller.

Ten minutes later, you’re deep in the weeds:

  • “How do I validate row 49,000 without crashing memory?”
  • “The client calls the column ‘E‑Mail’, but sometimes ‘Email Address’.”
  • “I need to find the Category ID by name, but create it if it doesn’t exist.”
  • “The client wants a ‘Dry Run’ to see errors before committing.”

Your controller becomes a 400‑line monster of while loops, try‑catch blocks, and manual validation logic. It’s hard to test, hard to read, and terrifying to refactor.

There has to be a better way.


Introducing Laravel Ingest

I built Laravel Ingest to stop this madness.

Laravel Ingest is a configuration‑driven framework for data imports. Instead of writing procedural scripts, you define what you want to import, and the package handles the how.

It takes care of the dirty work:

  • Streaming & Queues: Zero memory issues, whether 100 or 1 million rows.
  • Mapping & Transformation: Fluent API to map CSV columns to Eloquent attributes.
  • Relationships: Automatically resolves BelongsTo and BelongsToMany relations.
  • Dry Runs: Simulate imports to find errors without touching the database.
  • API & CLI: Auto‑generated endpoints and Artisan commands.

Requirements

  • PHP 8.3+
  • Laravel 10, 11, or 12

How It Works

Let’s say we want to import users. Instead of a controller, we create an Importer class.

1. The Declarative Config

fromSource(SourceType::UPLOAD)
    // Identify records by email
    ->keyedBy('email')
    // If email exists, update the record
    ->onDuplicate(DuplicateStrategy::UPDATE)
    // Map CSV 'Full Name' to DB 'name'
    ->map('Full Name', 'name')
    // Map CSV 'E‑Mail' (or 'Email') to DB 'email'
    ->map(['E‑Mail', 'Email'], 'email')
    // Transform 'yes/no' string to boolean
    ->mapAndTransform('Is Admin', 'is_admin', fn($val) => $val === 'yes')
    // Use Laravel validation rules per row
    ->validate([
        'Full Name' => 'required|string|min:3',
        'E‑Mail'    => 'required|email',
    ]);

2. Registering the Importer

In your AppServiceProvider, simply tag it:

$this->app->tag([UserImporter::class], 'ingest.definition');

3. Running the Import

You don’t need to write a controller for uploads. Laravel Ingest automatically provides API endpoints and Artisan commands.

Via CLI (great for cronjobs or S3 imports):

php artisan ingest:run user-importer --file=users.csv

Via API (for your frontend):

POST /api/v1/ingest/upload/user-importer
Body: file=@users.csv

The Killer Features

1. Handling Relationships Is Finally Easy

Usually, importing related data (like assigning a product to a category by name) requires annoying lookup logic. Ingest does it in one line:

->relate(
    sourceColumn: 'Category Name',
    relation: 'category',
    relatedModel: Category::class,
    lookupColumn: 'name',
    createIfMissing: true
)

2. Dry Runs Out of the Box

Clients hate it when an import fails halfway through. With Ingest, you can trigger a simulation that runs all validations and transformations but rolls back changes:

php artisan ingest:run user-importer --file=users.csv --dry-run

3. Error Analysis API

When rows fail, you don’t just get a log file. Ingest tracks failed rows in the database and provides an endpoint to download a CSV of only the failed rows, including error messages. Your users can fix the errors in Excel and re‑upload just those rows.


Under the Hood

Laravel Ingest stands on the shoulders of giants. It uses spatie/simple-excel to stream files line‑by‑line (keeping memory usage flat) and pushes chunks of rows onto the Laravel queue. This means your application stays responsive, even with massive imports.

Even when importing a 500 MB file.

Get Started

Installation is straightforward:

composer require zappzerapp/laravel-ingest
php artisan vendor:publish --provider="LaravelIngest\IngestServiceProvider"
php artisan migrate

Resources

Wrapping Up

I built this package because I was tired of writing the same fragile import code for every project. If you’ve ever dreaded the words “CSV upload”, give Laravel Ingest a try.

Found it useful? Star the repo on GitHub, open an issue if you have feature requests, or drop a comment below. I’d love to hear how you’re using it!

Back to Blog

Related posts

Read more »