Web application development
Laravel Application Development
New Laravel applications built to survive their own success - a schema designed before the data arrives, queues you can leave alone, and a second year cheaper than the first.
Laravel makes the first three months easy, and that is the problem. The framework will happily let you ship an application whose schema was invented one migration at a time, whose jobs assume they will only ever run once, and whose slowest query is fast because the table has four hundred rows in it.
None of that is visible at launch. All of it is visible at scale, and by then the data is in production and the fixes are outages.
What we build
Applications where the behaviour is the hard part: multi-tenant SaaS platforms, marketplaces with money moving through them, internal systems that replace a decade of spreadsheets, and back offices that a business actually runs on.
Where a problem has a shape of its own it has its own page - the database when the data model is the difficulty, queues when the work happens outside the request, and APIs when somebody else has to consume it. This page is the work those share.
The schema is decided before the code
Most Laravel applications get their database by accident. A model is created, a migration follows it, a relationship is added when a screen needs one, and eighteen months later there are four columns holding a status and no two of them agree.
We design it first, on paper, and it is the part of the engagement we are most opinionated about:
- Constraints in the database, not only in the application. A foreign key the schema enforces is a bug that cannot reach production. A rule that lives only in a form request is a rule that a queued job, an artisan command or a future developer will route around without knowing it existed.
- Indexes chosen from the queries, not guessed. Written when the query is written, while the reason is still in somebody's head.
- Migrations that are safe on a live table. Adding a column is free; adding one with a default, or an index, is a lock, and on a table with millions of rows a lock is an outage with a deploy attached to it.
- Money and time stored properly. Integer minor units, explicit currency, UTC, and a decision about timezone display written down once rather than argued about per screen.
Eloquent, used deliberately
Eloquent is productive and it hides the cost of what it does, which is a fine trade until the day it is not.
We treat the query count as a number somebody owns. Relations are eager-loaded because the code says so, not because a page felt slow once. Lazy loading is disabled outright in non-production environments, so an accidental N+1 is a failing test rather than a support ticket. Aggregates that belong in SQL stay in SQL instead of being collected into memory and counted in PHP.
None of that is exotic. It is the difference between an application that degrades gracefully and one that falls over on the day the marketing worked.
Work that happens outside the request
Anything slow, anything third-party and anything that can fail belongs in a job rather than in a controller. That is easy to say and it has consequences worth designing for rather than discovering:
Jobs are written to be safe to run twice, because at some point every one of them will be. A retry is not a failure mode, it is the normal case, and a job that charges a card or sends an email has to know that. Failures land somewhere a human can see them. Long-running work is chunked so a deploy does not kill it halfway.
Testing, in the amount that pays for itself
Feature tests over the routes that matter, unit tests where the logic is genuinely intricate, and a factory for every model so that writing the next test is cheap. Not one hundred percent coverage, which buys the wrong thing at a high price.
The measure we use is whether the suite would catch the change that breaks the business. A test that asserts a controller returns 200 catches almost nothing; a test that asserts an order cannot be paid twice catches the thing that would have cost you a refund and a reputation.
The shape of the work
Four phases. Each one finishes with something running on a URL rather than something described in a status report.
- Scope and schema. The domain written out, the tables and the constraints that hold them together, every system this has to talk to, and the list of jobs that will exist. Everything after this is priced against what comes out of it, which is why it produces a document and not a deck.
- The spine. Migrations, models, authentication, authorisation, the queue layout, the deploy pipeline, and a test suite running in CI from the first commit. Nothing looks finished. Everything underneath is.
- Domains, highest value first. Written to the conventions phase two settled, put through your review process if you have engineers, and released at the close of each iteration rather than piled up behind a launch date.
- Hardening and handover. Load testing where the numbers matter, a runbook for the calls that come at three in the morning, the schema written down, and a session per domain with the people who will own it.
All of it happens where your team already works - your repository, your tracker, your reviews. Code that stays inside our tooling until launch day is code your team first sees on the day it is too late to argue with.
What actually moves the number
The framework does not. The things that decide how long a Laravel build takes are the same things that decide how long any build takes, and being told which ones apply to you is more useful than a day rate:
- How many systems it has to talk to. One payment provider is a week. A payment provider, an ERP, a shipping API and a bank file format that is documented in a PDF from 2011 is a different project.
- Whether the rules are written down anywhere. Building against a process that only exists in one person's head is the most reliable way to build the same screen three times.
- Whether anything is already in production. Data that exists has to be migrated, and migrating data nobody has validated is where schedules go.
- How much of it is reporting. Reporting looks cheap and is not: it is where the query work, the date arithmetic and the "but finance counts it differently" conversations live.
When this is the wrong engagement
If an application already exists and the problem is that it has become difficult to change or is several versions behind, that is upgrades and rescue, and it starts by reading rather than writing.
If it works but falls over under load, that is performance and scaling.
And when you genuinely cannot tell which of the two you are looking at, that is what the audit is for - two weeks, a fixed price, and considerably cheaper than committing a quarter to the wrong one.
What you receive
The application, and the four documents that make it somebody else's to run: the schema with the reasoning behind every decision that was not obvious, the queue topology and what each worker is allowed to do, the deployment runbook, and the test suite with a note on what it deliberately does not cover.
Pull requests are reviewable and land continuously, so you read the work while it happens instead of receiving it at the end. If your team is going to take it over, they should be reviewing it before that day rather than after.
