# Building Speelgoedruilen.nl with Laravel: a case study

> How toy swap platform Speelgoedruilen.nl works: Laravel 13, Inertia and Vue, Reverb chat, AI photo checks, Mollie subscriptions and local SEO per town.

- URL: https://www.snoerendevelopment.nl/en/blog/building-speelgoedruilen-with-laravel
- Language: en-US
- Author: Michael Snoeren (Snoeren Development)
- Published: 2026-10-16

[Speelgoedruilen.nl](https://speelgoedruilen.nl) is a Dutch platform where families swap second-hand toys instead of buying new ones. I built it myself from idea to production with Laravel 13, Inertia with Vue, Laravel Reverb for real-time chat, Filament for the admin panel and Mollie for paid subscriptions. In this case study I walk through the choices I made and why, so you can see what goes into a marketplace or community platform.

**In short**

- **What:** a toy swap platform with profiles, listings, chat, trade requests, reviews and a Plus subscription.
- **Stack:** Laravel 13 on PHP 8.5, Inertia 3 with Vue and Tailwind CSS 4, Filament 5, Laravel Reverb, Laravel Cashier Mollie, Spatie Media Library and Laravel Nightwatch.
- **Stand-out feature:** a photo is all you need to list a toy. AI checks the photo and suggests a name, description, condition and categories.
- **Growth:** pages per town, email search alerts and instant notifications to search engines through IndexNow.

## Why a swap platform is a good test for your architecture

A marketplace looks simple: users post something, others respond. In practice, a platform like this touches almost everything that makes a web application hard. You deal with photo uploads, real-time messages, emails, payments, moderation, privacy and discoverability. And because children browse along with their parents, the content has to be safe as well.

That is why I chose the standard building blocks of the Laravel ecosystem from day one instead of home-grown solutions. The less custom work in the foundation, the more time is left for what makes the product unique.

## The stack: Laravel 13, Inertia and Vue

The application runs on **Laravel 13** and **PHP 8.5**. The frontend is built with **Inertia 3** and **Vue**, styled with **Tailwind CSS 4**. With Inertia you write regular Laravel controllers while users get a fast single-page app. You don't need a separate API for your own frontend, and routing and validation stay in one place.

A few choices that save a lot of time:

- **Laravel Wayfinder** generates TypeScript functions for routes, so a changed URL breaks the frontend build instead of becoming a dead link in production.
- **Spatie Laravel Data** with the TypeScript transformer makes sure the data a controller sends to Vue is typed in TypeScript as well.
- **Inertia optimistic updates** show a message or change on screen right away, before the server responds. If it fails, the change is rolled back cleanly.

## Real-time chat and trade requests with Laravel Reverb

Swapping starts with a conversation. Users chat with each other and send a trade request from that conversation, choosing which of their own toys they offer in return. Once the request is accepted, the toys on both sides are marked as swapped immediately.

The chat runs on **Laravel Reverb**, Laravel's own WebSocket server. Messages arrive instantly without depending on an external service such as Pusher. A new message appears right away, slightly faded until it is saved. If sending fails, the text simply returns to the input field.

Every trade request sends an email. To keep people from flooding others with them, each account has limits. Laravel makes this easy with its built-in rate limiter:

```php
RateLimiter::for('trades', function (Request $request): array {
    $key = $request->user()?->id ?? $request->ip();

    return [
        Limit::perMinute(2)->by("trades:minute:{$key}"),
        Limit::perDay(20)->by("trades:day:{$key}"),
    ];
});
```

Combining two limits stops both a quick burst and abuse spread over the day. Chat messages, reports, the contact form and the photo check have similar limits.

## AI photo checks: from photo to listing

The biggest hurdle on any marketplace is creating a listing. Nobody wants to come up with a title, description and category for a box of Duplo. That is why a photo is enough on Speelgoedruilen.nl.

After the upload, an AI model (Anthropic's Claude) reviews the photo. It checks whether the photo suits a family platform and whether it actually shows a toy. It also suggests a short name, a factual description, the condition and up to three categories. The user only has to review the suggestions and hit publish.

A few design choices make this reliable in production:

- **Structured output.** The model answers according to a fixed JSON schema in which categories are limited to the ones that actually exist in the database. No made-up categories come back.
- **Factual, no sales talk.** The instructions forbid hype, exclamation marks and prices. People swap here, they don't sell.
- **Caching by photo.** The result is stored for 24 hours under a hash of the image. Uploading the same photo again costs no extra API call.
- **Fail open.** If the API is unreachable, the error is logged and the user fills in the fields themselves. An outage at an external service must never block listing a toy.
- **Testable.** API calls go through Laravel's HTTP client, so they can be simulated in Pest tests with `Http::fake()`.

```php
try {
    $analysis = $this->request($key, $image, $categories);
} catch (\Throwable $exception) {
    // An outage of the API should not stop users from offering their toys.
    report($exception);

    return ImageAnalysis::unknown();
}
```

## Subscriptions with Mollie and an admin panel with Filament

The free account has limits on the number of toys and trade requests per month. With **Plus** (€2.99 per month or €24.99 per year) the limits are higher and you see new toys earlier. Payments run through **Laravel Cashier Mollie**, including invoices with address, VAT and Chamber of Commerce numbers. Limits and prices live in configuration, so the text on the website, the subscription page and the terms of service always match.

The admin panel is built with **Filament 5**. Admins see widgets with trade requests and revenue, handle reports from users and can temporarily log in as a user to investigate issues. Important events, such as a new account or a new subscription, also arrive in Discord through a webhook.

## Discoverability: local SEO per town

People prefer to swap toys with someone nearby. That is why every town has its own page, such as `/in/utrecht`, listing the toys offered there. This matches exactly how parents search: "swap toys in Utrecht".

To get those pages into search engines quickly, new, changed and removed toys are reported every hour through **IndexNow**. Bing and other search engines update those pages faster as a result. Google uses the sitemap. The platform also has an `llms.txt`, so AI assistants such as ChatGPT and Claude can explain the platform accurately when someone asks about it.

Users can turn on a search alert per town. They then receive at most one email per day with new toys in that town. That keeps visitors engaged without flooding their inbox.

## Security, privacy and maintenance

A platform for families calls for extra care. A few measures I built in:

- **Sign-in** with two-factor authentication or passkeys through Laravel Passkeys.
- **Abuse limits** of one registration per day and five new listings per day per account.
- **Cleanup** of unverified accounts after 7 days, and of deleted toys and conversations after 30 days through `model:prune`.
- **Analytics** with Matomo, hosted in the Netherlands, sending only an account ID and never a name or email address.
- **Monitoring** with Laravel Nightwatch, so errors and slow queries surface before users complain.

I guard quality with **Pest**, including browser tests in Firefox and Safari and mutation tests, alongside PHPStan through Larastan. Every change goes into a changelog, and a `/changelog` skill turns it into a plain-language "What's new" page for users.

## What you can take away for your own platform

1. **Use the ecosystem.** Reverb, Cashier, Filament and Nightwatch save weeks of work compared to building it yourself.
2. **Remove friction.** A photo instead of a form is the difference between an empty marketplace and a lively one.
3. **Never let external services block your core flow.** Design AI and other integrations to fall back quietly.
4. **Think about discoverability from the start.** Local pages, IndexNow and `llms.txt` cost little and bring in visitors steadily.
5. **Rate limit everything that sends email or costs money.** In Laravel, that takes a few lines of code.

## Frequently asked questions

### What technology does Speelgoedruilen.nl use?

Speelgoedruilen.nl runs on Laravel 13 and PHP 8.5, with Inertia and Vue for the frontend, Laravel Reverb for real-time chat, Filament for the admin panel and Laravel Cashier Mollie for subscriptions.

### How does the AI photo check work?

After the upload, an AI model checks whether the photo suits families and shows a toy. It suggests a name, description, condition and categories, which the user can edit. If the AI is unavailable, the user fills in the fields themselves.

### Is Laravel a good fit for a marketplace or community platform?

Yes. Laravel has official packages for real-time communication, payments, authentication and monitoring, and Filament lets you build an admin panel quickly. That gets an MVP live fast and lets the platform keep growing afterwards.

## Want a platform built?

Do you have an idea for a marketplace, community or SaaS product? I build platforms with Laravel from idea to production, based in Oosterhout, the Netherlands, for clients in the Netherlands and beyond. See what I do in [SaaS platform development](/en/services/saas-platform-development) or [MVP development](/en/services/mvp-development), or [get in touch](/en/contact) to discuss your idea.