Marreta Lang: A Zero-Ceremony Language for REST APIs
A large part of backend work is still REST APIs that receive data, validate it, apply a rule, call common infrastructure, and return a clear answer. Marreta Lang is a focused domain-specific language (DSL) for exactly that: a language built for one job rather than a general-purpose one that does everything, interpreted by a compiled native runtime. This is the story of why we built it, what the language looks like, and the developer experience it is built around.
Abstract
Marreta Lang is a domain-specific language for building REST APIs. HTTP routes, request validation, responses, reusable tasks, relational and document databases, cache, queues and topics, authentication, scenario tests, and generated OpenAPI docs are first-class language and CLI concepts. The language is interpreted by a compiled native runtime, and infrastructure is configured through the environment rather than wired in code. The idea is to write the API behavior, not the framework plumbing.
It began as a research project at TM Dev Lab to validate three hypotheses at once: low and predictable resource usage, strong performance despite a high level of abstraction, and good developer experience. After introducing the language, this article shows the evidence for each hypothesis from the language itself, on its own terms rather than as a comparison: where the resource and performance profile comes from (a high-level surface on a compiled native runtime, with its own measured figures), and the developer experience the design is built around (one place to express a feature, its contract, and its tests).
Table of Contents
1. Origin and Motivation
Marreta Lang began as a research project at TM Dev Lab, exploring whether a new language could validate three hypotheses at once. The premise is that a large share of backend work is still about REST APIs that receive data, validate it, apply business rules, call common infrastructure, and return clear responses. Many of those services matter, but they do not always need a pile of architectural ceremony. The same setup and glue code gets rewritten every time: a database client, a serializer, a validation layer, a message consumer, a cache wrapper, an OpenAPI generator, all wired together before a single line of business behavior is written.
The three hypotheses pull against each other in most stacks. A high level of abstraction usually costs performance and resources. A small footprint usually costs developer convenience. The question the project set out to answer was whether a purpose-built language on a compiled native runtime could refuse that trade-off.
| Hypothesis | What it claims |
|---|---|
| H1: Low, predictable resource usage | Efficient consumption, especially in containers, with a footprint that stays small under load and varies little across runs. |
| H2: Strong performance | Even as a high-abstraction DSL, the HTTP hot path stays fast and stable under load. |
| H3: Good developer experience | Integrate SQL, NoSQL, messaging, cache, HTTP, and OpenAPI without redoing the same boilerplate, with zero project dependencies. |
Table 1: The three hypotheses, taken verbatim from the language's about page. The rest of this article shows the evidence for each, drawn from the language itself rather than from a comparison.
2. Meet Martim
Before the code, meet the mascot, because he captures the whole idea better than a spec section can. Marreta is the Brazilian Portuguese word for a sledgehammer. The name is kept in Portuguese on purpose: it preserves the project's Brazilian origin rather than hiding it behind a generic English brand. And the metaphor is the point, a focused tool that breaks through boilerplate to reach the code that actually describes the API behavior.
The legend: there was a time when every API was born buried under layers, scaffolding, and configuration files, and no one could see what it actually did anymore. Then came Martim, a sledgehammer of steel, soul, and no patience for ceremony. One swing was enough for the excess to collapse, leaving only the essentials standing: take the request, apply the rule, return a clear answer. Everywhere he passed, the code went back to telling its own story.
Do not be fooled by the happy little grin. Beneath it lies pure steel, and wherever Martim passes he leaves more than clean code: he leaves quality, efficiency, and performance. He may not be the hero your project asked for, but he is the one it needed. Zero ceremony does not mean hiding behavior. It means removing repetitive wiring while keeping the API behavior visible in the source. That distinction is the whole design, and the rest of this article is what it looks like in practice.
3. What Marreta Is
3.1 Routes, validation, responses
A Marreta route reads like the behavior it describes. There are no imports, no framework setup, and no connection boilerplate. The route below fetches an account and guards the not-found case in four lines.
route GET "/accounts/:id"
account = doc.accounts.find(params.id)
require account else fail 404, "account not found"
reply 200, account
Validation, persistence, and a typed response are equally direct. A schema declares the
shape, and binding it to a route with take payload as
validates the incoming body before the route body runs. An invalid body returns a 422
automatically, so the route body only ever sees data that already matches the contract.
schema NewAccount
owner: string
route POST "/accounts" take payload as NewAccount
account = doc.accounts.save({ owner: payload.owner, balance: 0 })
reply 201, account
3.2 Providers: one namespace per concern
The idea is to work in terms of building blocks. Integration components such as databases, message buses, and caches are exposed as providers: high-level abstractions that hand the developer a native language namespace, one per concern, for using each component without implementing any of the integration. You choose a provider and set its connection details in a configuration file, and once the project starts you simply use the namespace. The application code never names a concrete technology.
graph TD
subgraph App["Application code (.marreta)"]
R["route / task / scenario"]
end
subgraph NS["Language namespaces"]
DB["db.*"]
DOC["doc.*"]
CACHE["cache.*"]
Q["queue.* / topic.*"]
end
subgraph CFG["marreta.env (configuration)"]
E["provider + connection details"]
end
R --> DB
R --> DOC
R --> CACHE
R --> Q
DB -.bound at startup.-> E
DOC -.bound at startup.-> E
CACHE -.bound at startup.-> E
Q -.bound at startup.-> E
Figure 1: Application code uses a namespace per concern. The concrete provider and its credentials are bound from configuration at startup, never referenced in the source.
A provider can have more than one implementation, so the same namespace could be backed by different technologies. In the current release each concern ships with one provider: a relational database, a document database, a cache, and messaging for point-to-point and pub/sub. The resource and performance hypotheses come from this design: a high-level, abstracted language surface delivered on a compiled native runtime.
3.3 Tests as a first-class concept
Endpoints are tested against mocked infrastructure with no separate test harness. A scenario reads like the behavior it checks, and runs in memory, which is what makes the test feedback loop fast (a point we return to in the developer-experience section).
scenario "returns an account by id"
given doc.accounts.find("acc_1") returns { owner: "Ana", balance: 500 }
when GET "/accounts/acc_1"
then status 200
then response is { body: { owner: "Ana", balance: 500 } }
4. A Compiled Native Runtime
This is where the first two hypotheses are answered, H1 (resource usage) and H2 (performance), and both come from one architectural choice. The distinction is easy to blur, so it is worth stating exactly: the language is interpreted, but the runtime that interprets it is compiled to a native binary. The program you write is read and executed by the engine, and the engine itself is native machine code, not a managed virtual machine.
Two consequences follow from that, and both are the engine's behavior rather than anything the developer configures. First, there is no garbage collector. Memory is released deterministically as values leave scope, so the resident set stays small and flat and there are no collection pauses. Second, requests are served on an asynchronous, multi-threaded I/O model, so many concurrent connections share a small pool of worker threads rather than costing a thread each. There is also no per-project dependency tree to load at boot, which is most of why the cold start is short.
That is the whole positioning, stated plainly: the developer experience of a high-level language with the resource profile of a native one. It is a category of runtime, not a trick. The figures below are what that category looks like in practice for a Marreta service.
| Property | Marreta service | Why it matters |
|---|---|---|
| Idle memory footprint | about 4.3 MiB | Density in containers, cheaper to run many small services |
| Time to first request | about 1 second | Serverless and scale-from-zero, fast restarts and deploys |
| p99 latency at 1,000 req/s | under 3 ms, zero errors | The hot path stays flat under real load, not just when idle |
| Sustained throughput on one core | up to about 1,250 req/s | Where the service becomes CPU-bound on a single-CPU budget |
| Run-to-run variance | low (near-flat) | Predictable matters as much as low, for capacity planning you can trust |
Table 2: The resource profile of a Marreta service, measured with the app in a container capped at 1 CPU and 1 GB, against a shared document store given headroom, under a load weighted toward a read-heavy transaction-history listing. The p99 figure is at the 1,000 req/s level, and throughput is sustained until the single core saturates near 1,250 req/s, where latency naturally climbs. These are our own measurements of the language's behavior, not a ranking against any other stack.
None of this asks the developer to think about the runtime. You write routes, schemas, and tasks, and the footprint and the startup time are a property of the binary they run on. The point of the architecture is that the high-level surface in section 3 does not have to be paid for in resources, which is the trade-off most abstractions force. On the language's own measurements, H1 and H2 read as supported, with no claim made here about how any other stack behaves.
5. Developer Experience
This is the third hypothesis, H3, and the strongest reason to reach for Marreta. The resource profile is what the runtime buys you, and the developer experience is what the language is actually about. The best way to show it is not an adjective but an artifact: a whole feature, its input contract, the OpenAPI it generates, and the test that proves it, all in one place.
5.1 One feature, end to end
Here is a deposit endpoint. It declares the shape of its input, validates the body and the business rule, reads and writes the document store, and returns a typed response. There are no imports, no database client, and no serializer to wire up.
export schema Deposit
amount: integer
route POST "/accounts/:id/deposit" take payload as Deposit
require payload.amount > 0 else fail 422, "amount must be positive"
account = doc.accounts.find(params.id)
require account else fail 404, "account not found"
new_balance = account.balance + payload.amount
doc.accounts.update(params.id, { balance: new_balance })
reply 200, { id: params.id, balance: new_balance }
Because the route declares its input with a schema, the OpenAPI document is generated from
the same source, with no second file to keep in sync. This is an excerpt of what the route
above produces, taken from the generated /openapi.json: the
path parameter, the request body referencing the schema, and the schema as a named
component.
"/accounts/{id}/deposit": {
"post": {
"parameters": [
{ "name": "id", "in": "path", "required": true,
"schema": { "type": "string" } }
],
"requestBody": {
"required": true,
"content": { "application/json": {
"schema": { "$ref": "#/components/schemas/Deposit" } } }
}
}
},
"components": { "schemas": {
"Deposit": {
"type": "object",
"properties": {
"amount": { "type": "integer", "format": "int64" }
},
"required": ["amount"]
}
} }
The same generation also emits, for this route, an automatic 422
response for a body that fails validation, the success response shape, an
operationId, and a tag, all from the route and the schema
rather than a hand-maintained spec. And the same feature is tested. The scenario runs in
memory against a stubbed provider, so it needs no database and returns in milliseconds. It
reads like the behavior it checks.
scenario "a deposit increases the balance"
given doc.accounts.find("acc_1") returns { balance: 500 }
given doc.accounts.update("acc_1", anything) returns true
when POST "/accounts/acc_1/deposit" with { amount: 1000 }
then status 200
then response is { body: { id: "acc_1", balance: 1500 } }
Three concerns, one place: the input contract, the behavior, and the test. The OpenAPI is a byproduct, not a parallel artifact. That density is the developer experience, and it is easier to count by reading the code than to argue in prose.
5.2 What comes in the box
The reason a feature fits in one place is that the concerns a REST service needs are part of the language and the CLI, not libraries assembled per project. A Marreta project starts with zero direct dependencies.
| Capability | How Marreta provides it |
|---|---|
| Relational and document databases | Built-in db. and doc. namespaces |
| Cache, queues, and topics | Built-in cache., queue., and topic. namespaces |
| HTTP client | Built-in http_client. namespace |
| Validation and typed responses | Built-in, through schema and take ... as |
| OpenAPI documentation | Generated from the routes and schemas, no second file |
| Scenario tests | Built-in, in-memory, against stubbed providers |
| Provider portability | Swap the backing technology in configuration, not in code |
| Toolchain | One binary: init, serve, test, fmt, lint, doctor |
Table 3: The concerns a REST service needs, provided by the language and the CLI rather than assembled from libraries per project.
The test feedback loop is worth a number, because it follows directly from the in-memory scenario runner: an equivalent provider-free suite runs in about 0.02 seconds. A test loop that fast is one you actually keep running while you work, which is the practical half of the developer-experience goal.
6. Conclusion
The three hypotheses read as supported, each on the language's own evidence rather than on a comparison. H1 and H2, resources and performance, follow from the architecture: a high-level surface on a compiled native binary, with a small and predictable footprint and a fast hot path on a single core. H3, developer experience, is the feature you saw written, documented, and tested in one place. The language surface is high level and the runtime it runs on behaves like a native process, so the convenience of an abstraction does not have to be paid for in resources.
For the common REST case, Marreta lets you write the behavior instead of the wiring, and run it with a small, predictable footprint. It is not meant to replace every backend stack or become a general-purpose language. If a service needs an unusual runtime model or low-level control, another stack fits better. For the common REST case, that is the space Marreta is built for. One swing, and the excess collapses.
A word of honesty to close. Marreta Lang is a new project, launching publicly at version 0.2.0. We know there is a great deal still to evolve, the language and its surface are not frozen, and there are rough edges we have not found yet. But the early results were promising enough that we decided to open it to the community now, in the open, rather than keep polishing it in private. If the idea resonates with you, we would love your help: try it, build something small, tell us where it falls short, and contribute. A language is only as good as the community around it, and that part starts today.
Get started.
- Language site and documentation: marreta.dev and marreta.dev/docs
- Install the runtime:
curl -fsSL https://raw.githubusercontent.com/tm-dev-lab/marreta-lang/main/install.sh | sh - The language source, runtime, and CLI: the marreta-lang repository
References
- Marreta Lang. Language site and documentation. https://marreta.dev
- Marreta Lang. The language source, runtime, and CLI. https://github.com/tm-dev-lab/marreta-lang