Kooko delivered meals from local restaurants around Lausanne. The food was prepared on site, carried by salaried bicycle couriers and packed in compostable containers. What began as a dinner service for individuals gradually became a lunch service for teams and, eventually, a platform sold directly to their employers.
Kooko was also our own company. We co-founded it and built every version of the product, so this is not a conventional client case study. It is the story of five years spent watching a business change faster than the system underneath it—and learning that a good architecture can still become the wrong one when the customer changes.
First, we delivered dinner to one person
The original promise was deliberately narrow: Lausanne, in the evening, with food from the city’s own restaurants delivered by bike. At that stage, the priority was not to build a universal marketplace. It was to launch quickly, keep infrastructure costs low and make a small catalogue feel immediate on a phone.
We compiled the entire shop with Gatsby. Products, restaurants, categories and sales channels were read from Sylius during the build, then Capacitor packaged the result for the web, App Store and Play Store.
{ resolve: "gatsby-source-custom-api",
options: { url: process.env.GATSBY_SYLIUS_CHANNEL_URL, rootKey: "SyliusChannel" } },
{ resolve: "gatsby-source-custom-api",
options: { url: process.env.GATSBY_SYLIUS_PRODUCT_URL, rootKey: "SyliusProductBeta" } },
{ resolve: "gatsby-source-custom-api",
options: { url: process.env.GATSBY_SYLIUS_VENDOR_LAUSANNE, rootKey: "SyliusVendor" } },A static catalogue sounds like an odd choice for restaurants whose menus change regularly. The solution was to update the application without waiting for another store review: every build produced a downloadable web bundle, and the installed app checked for a new version as people moved through it. A menu change meant a build and a lightweight bundle update, not a new mobile release.
A restaurant should be able to change its menu without waiting for Apple.
Only stock, cart and payment remained live. For one city and one delivery period, this was fast, inexpensive and appropriately simple. Its limits were equally clear: the evening was hard-coded into the build, and one cart could contain food from only one restaurant. At the time, that matched the operation perfectly—one kitchen, one bag, one bicycle.
Then one order became a group decision
The delivery minimum changed the product. A person ordering dinner rarely reached it; three colleagues ordering together usually did. But colleagues do not necessarily want food from the same place, and a basket shared by several people cannot live inside one browser.
The second Kooko app was therefore built around a server-side, shareable order. One person started the basket, sent its link to the table, and everyone added their own meal—even from another restaurant. The order was confirmed once the group reached the minimum.


We also separated editorial content from commerce. Strapi managed restaurant pages and menus at the pace the team needed; Medusa managed products, prices, baskets and payments transactionally. Each restaurant received its own store and access, while a small relation joined the editorial menu to the corresponding commerce product.
A cart shared by a table is no longer browser state. It is part of the product.


The interface changed with the ordering model. The first application asked what someone wanted to eat that evening. The second helped a group choose among restaurants and cuisines. Technology followed a genuine shift in customer behaviour rather than an abstract desire to rebuild.
The customer became the employer
The next change came from delivery economics. Fifty meals delivered to fifty homes require fifty journeys. Fifty meals delivered to one office require one. That difference moved Kooko from selling dinner to individuals to selling lunch programmes to companies.
A corporate account added to the public marketplace would not have been enough. Each company needed its own restaurants, negotiated prices, payment methods and ordering rules. We built a dedicated portal whose context began in the URL: the company slug resolved its catalogue, customer group, subscription and checkout configuration.
company: defineRoute('/[companySlug]', {
params: z.object({ companySlug: z.string() }),
}),
cart: defineRoute('/[companySlug]/cart', {
params: z.object({ companySlug: z.string() }),
search: z.object({ cart_id: z.string().optional() }).default({}),
}),That gave employees a portal tailored to their workplace. One company paid by invoice; another offered card or Twint. One ordered from a small selection near its office; another had a broader catalogue. The shared-order concept from the consumer app became a quotation and pre-order flow suited to corporate approval.






The automation behind the portal was one of the project’s strengths. A company arriving through the public form could become a qualified prospect, receive its own record and portal, and start with a catalogue assembled from restaurants around its office.

But the system had become heavier with every successful answer. ERPNext handled subscriptions, pricing rules and stock. Medusa handled commerce. Strapi handled content. Directus handled operational data, logistics and internal views. n8n synchronized the four. This was a sensible way to prove that companies wanted the service, but it was not a sensible way to open city after city.
Geneva forced us to ask a better question
When Geneva became the next market, the architecture answered with another deployment, another commerce instance, another schema and another portal to maintain. We had designed tenancy as infrastructure: every new market meant another copy of the system.

In 2022, each client received its own backend and database. 
In 2025, each city still implied another commerce deployment, schema and portal.
The tooling reinforced that model. Medusa’s first major version assumed one store per installation. We had already removed a database constraint simply to support one store per restaurant—a clear indication that Kooko’s business model had outgrown the model offered by the platform.
A second market should add data, not infrastructure.
The fourth architecture began with that principle. A market, restaurant, company, courier, menu or quotation had to become a record inside one platform. Relations would replace synchronizations, and a new city would become configuration rather than a parallel deployment.

This time, we deliberately wrote less. Medusa’s second version provided modular commerce; Mercur already handled marketplace sellers, split orders and payouts; an existing B2B foundation covered companies, employees, approvals and quotations. Our task was to combine them and add only what was specific to food delivery: menus, couriers and delivery rounds.


What Kooko changed in the way we build
Geneva was meant to prove the fourth model before Lausanne migrated onto it. Kooko stopped trading before that launch. The architecture remained a design, not a production success, but the reasoning behind it became one of the most useful outcomes of the company.
Five years taught us that architecture is not a permanent expression of technical taste. It is an answer to who the customer is, how money moves and what the business needs to repeat. The static application was right for one city and one evening offer. The shared cart was right when colleagues ordered together. The corporate portal was right while Kooko proved its B2B model. The marketplace design became necessary only when opening a second city exposed the cost of duplicating everything.
That experience still guides our choices. For the Cabinet des Alpettes, the right answer was the lightest CMS that gave the practice autonomy. For Autocorner, it was a shared data layer capable of reconciling brands, centres and live inventory. Kooko taught us to tell the difference early—and to build only as much platform as the next real step requires.