Skip to content

Laravel in Frankfurt

Laravel development for Frankfurt banks and insurers

A Laravel development company for Frankfurt financial firms. An audit trail that records reads, retention periods that outrank a deletion request, and four-eyes approval living in the schema.

Frankfurt is where a backend stops being judged on what it does and starts being judged on what it can prove.

The buyers are banks, insurers, asset managers and payment institutions. Their software is not merely expected to work - it is expected to account for itself to somebody who was not there, months later, from records that were written at the time. That single requirement reshapes a Laravel application more than any performance target ever will.

A Frankfurt log has to record reads, not just writes

Most applications log changes. Financial institutions need to know who looked.

A customer record opened by an advisor is an event worth recording, and nothing in the framework records it, because the overwhelming majority of applications have no reason to care. So it becomes a deliberate design: a single read path through which customer data is fetched, a table that captures who asked, when, for which record and under which stated reason, and a query log that holds the actual statement rather than a reconstruction of it.

Doing this at the start costs a week. Doing it after an inspection asks for two years of access history means answering that the application does not know.

Retention and erasure point in opposite directions in Frankfurt

Here is the conflict that reaches the schema, and it is not solvable in a privacy policy.

German commercial law requires certain records to be kept for years. Data protection law gives a person the right to have their data erased. Both apply, and an application that implements only the second one deletes something it was required to produce later.

What resolves it is a third state. The record is neither live nor gone - it is restricted, readable only by the process with a legal basis to read it, excluded from every ordinary query, with the restriction recorded as an event in its own right. That is a real column, a global scope and a documented exception, not a flag.

It is also why a soft delete is not a delete is required reading before anybody writes the migration. A deleted_at column satisfies neither duty. It hides a row from the queries that remember to filter and leaves it readable by everything that forgets, which is the worst of both outcomes.

Approval belongs in the Laravel data, not the screen

The four-eyes requirement is routine in Frankfurt and routinely implemented wrongly.

An approval that lives in the interface is a habit. A console command, a scheduled job or a future developer in a hurry will write the row directly, and nothing in the system objects. What holds is a pending-change record - proposed values, who proposed them, who approved, when - where the live row is only ever written by the transition that applies it.

The same logic applies to permissions generally. Authorisation is not authentication, and in a supervised institution the difference is not academic - the question asked is whether an export, a report or an internal tool could reach records its operator was not entitled to see.

A Frankfurt outsourcing arrangement reaches into the code

When a supervised institution buys software work, the supervisory expectations around outsourcing come with the purchase, and some of them land on the application rather than on the contract.

Audit rights are the clearest example. The institution has to be able to have the arrangement examined, which in practice means the supplier can produce evidence rather than assurances - who had access to what, which changes were deployed when, how a release was approved, where data was processed. An application that already records those things answers an audit in a week. One that does not produces an exercise in reconstruction.

Sub-processors have to be enumerable, and this is where most applications disappoint. Every third party the data reaches is part of the chain, including the ones that look like tooling, and the institution needs the list with purposes and locations rather than a sentence saying there are none. Keeping that list as the thing the application reads its integrations from, rather than as a description of them, is what keeps it true.

An exit plan is required and it is a technical artefact. What the institution receives if the arrangement ends - the code, the data in a documented format, the infrastructure definitions, the credentials - and how long it takes. The honest version of that document mentions what would be difficult, which is worth more than a version that claims everything is portable.

Laravel test data for a Frankfurt firm, without the production database

The quickest way to get realistic test data is a copy of production, and in a supervised institution it is also the quickest way to create an incident.

A production copy on a developer's laptop is a disclosure waiting for a lost bag, and it is not made acceptable by being on an encrypted disk or by a promise to delete it. So the working assumption is that production data does not leave production, which means the test environments need data that is realistic without being real.

Anonymisation is harder than replacing names, because referential integrity has to survive it. The same customer has to be the same customer across six tables or the data no longer exercises the code paths that matter, so the transformation is deterministic per identifier rather than random per row. Dates get shifted consistently rather than individually, or the sequence of events stops making sense. And free-text fields are the part people forget - a note field holds names, account numbers and sometimes a password, and no field-level rule catches that reliably, which is a good argument for dropping free text in test data rather than attempting to clean it.

Generated data is the better answer where it can be made to work. A factory that produces a plausible portfolio - accounts with histories, transactions with distributions that look like the real thing, the awkward edge cases deliberately included - gives the test suite something production data never does, which is cases that are known in advance and reproducible. The edge cases are the point. A production copy contains the ones that have already happened; a generated set can contain the ones you are worried about.

Commercially

Scope fixed in writing before work starts, priced per project. The country-level ground - what German data protection expects of a supplier, the e-invoicing format, hosting - is on the Germany market page.

Invoiced by Codefacture Yazılım A.Ş. in Türkiye, in euros, by bank transfer to an IBAN, with no Turkish VAT under the export-of-services exemption. That entity name is what goes in your outsourcing register, so it is worth having early.

The regulatory and commercial ground for this city is on the Germany market page.

Frequently asked questions

We need an audit trail. Is that not what model events are for?
Model events give you writes, which is the easy half. What a supervised institution is usually asked for is reads - who opened which customer record, when, and under what justification - and nothing in a framework records that for you because most applications have no reason to. It is a deliberate table, written through a single code path, with the query that produced it recorded rather than reconstructed. Bolting it on later means auditing an application that cannot tell you what it already showed people.
What happens when a deletion request hits a record we are required to keep?
This is the Frankfurt problem in one sentence, and the resolution is structural. A record under a statutory retention duty cannot be deleted on request, so it moves to a restricted state - readable by the one process that needs it, invisible to everything else, with the restriction itself recorded and dated. A `deleted_at` column does not express that, because a soft delete is a filter anyone can forget.
Our process requires two people to approve a change. Where does that live?
In the data model, not in the interface. An approval that exists only as a screen is a convention, and conventions are bypassed by a console command or a scheduled job eventually. What works is a pending-change record that holds the proposed values, the proposer, the approver and the moment of approval, with the live row updated only by the transition. It is more work and it is the only version that survives an inspection.
Can this run inside our own infrastructure in Frankfurt?
Usually it must, and it is a normal way to run Laravel rather than a compromise. Queue, cache and database inside your network, deployment through your pipeline, no external service holding a payload. The one thing worth deciding early is what the queue payloads contain, because a job argument carrying customer data is a copy of that data sitting in Redis where nobody counted it.
Call us+1 848 272 7583WhatsApp+90 850 308 5436Emailinfo@codefacture.comContact page