I Won a Hackathon Gold Medal With This Zero-Cost BFF Framework — But Here's What Nobody Tells You
Honestly, I didn't expect to win.
It was a cold Saturday morning at the company hackathon, and my team was stuck. We had this great idea for a mobile app that needed to aggregate data from half a dozen backend services, but we only had 24 hours to build everything. If we went the traditional route — writing a separate BFF layer with all the controllers, DTOs, and service orchestration — we'd burn 8 hours just on boilerplate code. That's half our time gone before we even started on the actual product features.
Then I found it: Capa-BFF. A zero-cost BFF framework that promised to give us a complete BFF layer with almost no code. I was skeptical, but desperate times call for desperate measures. We integrated it in 10 minutes. And three weeks later, we were standing on stage accepting the gold medal.
But here's the thing — nobody talks about the catch. Yes, it's zero-cost in terms of money. No, it's not zero-cost in terms of learning, debugging, and trade-offs. I've been using it in production for three months now, and I want to share the real story.
What Even Is Capa-BFF?
For those who don't know, BFF stands for Backend For Frontend. It's a pattern where you create a separate backend service specifically tailored to your frontend's needs. Instead of having your frontend aggregate data from multiple backend services directly, the BFF does it for you. This gives you a cleaner API, reduces round trips, and keeps your frontend code simpler.
Capa-BFF is an open-source framework that implements this pattern with a very interesting approach: zero Java code required. Instead of writing controllers and service classes, you use a lightweight DSL to define your API endpoints and how they aggregate data from upstream services.
Here, let me show you what it looks like:
# Capa-BFF DSL example - get user profile with recent orders
get /api/user/profile:
services:
- name: userService
method: GET
path: /users/{userId}
params:
userId: "#{query.userId}"
- name: orderService
method: GET
path: /orders/recent
params:
userId: "#{userService.data.id}"
compose: |
{
user: #{userService.data},
recentOrders: #{orderService.data},
timestamp: #{new Date().getTime()}
}
That's it. That's a complete endpoint. Capa-BFF handles the HTTP calls, parameter binding, response composition — everything. You just drop this YAML file into your project, and boom — you have a working BFF endpoint.
I know what you're thinking: "That's magic! How can this be zero code?" Well, it runs as a Spring Boot starter. You add the dependency to your pom.xml:
<dependency>
<groupId>com.capa-cloud</groupId>
<artifactId>capa-bff-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
Add some configuration for your upstream services:
capa:
bff:
services:
userService:
url: http://user-service:8080
orderService:
url: http://order-service:8081
And that's literally it. Your BFF is up and running. I timed it once — from adding the dependency to having the first endpoint working took exactly 7 minutes. Seven minutes! That's insane for a complete BFF layer.
The GitHub is here if you want to check it out: https://github.com/capa-cloud/capa-bff
The Good Stuff: What It Does Really Well
Okay, let's start with the pros because there's a lot to like here. I genuinely think this project is clever, and it solved our hackathon problem perfectly.
1. It's actually zero boilerplate
I've built BFF layers the traditional way many times. You know the drill:
- Create a new Spring Boot project
- Add all your dependencies (Web, Security, Actuator, etc.)
- Create a DTO class for the request
- Create a DTO class for the response
- Create a controller class
- Create a service class that calls all the upstream services
- Write error handling
- Write tests
That's easily 100-200 lines of code just for one endpoint. With Capa-BFF, it's 20 lines of YAML. That's it. No classes, no compilations, no nothing. Just save the file and it hot-reloads. (Yes, it supports hot-reloading in development. That's chef's kiss for hackathons.)
2. It's perfect for rapid prototyping
When you're building a prototype or a hackathon project, you don't care about perfect architecture. You care about shipping something that works fast. Capa-BFF shines here. We went from idea to working prototype in 4 hours instead of 12. That extra time let us work on the mobile UI and the user experience, which is actually what won us the competition.
3. The learning curve is actually gentle
Wait, hear me out. A new DSL sounds scary. But the DSL is tiny. There are only about 10 concepts you need to learn: get/post for HTTP methods, services for upstream calls, params for binding, compose for response formatting. That's it. I went from zero to writing my first endpoint in under 15 minutes. That's way better than learning a whole new framework.
4. It plays nicely with your existing Spring Boot app
You don't have to create a separate service. You can just add the starter to your existing Spring Boot app and start adding BFF endpoints alongside your other controllers. We did this for our internal admin dashboard — added Capa-BFF to our existing backend, dropped in a dozen YAML files for the frontend, and done. No new services to deploy, no extra infrastructure to manage.
5. Did I mention it's free?
It's open-source, MIT licensed, completely free. No enterprise tiers, no feature locks, nothing. You can use it in commercial projects for free. For startups and hackathons, that's a big deal. We didn't have to pay anything, and we still don't.
The Not-So-Good Stuff: What They Don't Advertise
Okay, now let's get real. This is where I tell you the stuff that isn't in the README. I learned the hard way so you don't have to.
1. "Zero Java code" doesn't mean "zero learning curve"
Wait, I just said the learning curve is gentle. That's true for simple use cases. But when you need to do anything custom — like authentication, or custom error handling, or complex composition — you suddenly need to understand how Capa-BFF works under the hood. And that's when you realize: you've added a whole new layer of abstraction that you need to debug when things go wrong.
We hit this on day two of the hackathon. We needed to add some custom request headers to upstream services based on the incoming request. Turns out you can do it — but you need to write a Java filter. So much for zero code.
2. No type checking = runtime errors that are hard to debug
Here's the biggest gotcha. Since everything is in YAML strings, there's no compile-time checking. If you misspell a service name, or get a parameter name wrong, you don't find out until you hit the endpoint at runtime.
I can't tell you how many times I've done this:
params:
userId: "#{userServce.data.id}" # Oops — typo: "Servce" instead of "Service"
The error message you get is something like "Cannot resolve property 'userServce'", which is helpful, but wouldn't you rather catch this at compile time or in your IDE? With the current tooling, you don't get that. Your IDE just sees it as YAML text. No autocomplete, no refactoring, nothing.
3. Performance takes a hit — it's not free lunch
So here's the numbers. I ran a simple benchmark on my laptop:
| Setup | Avg Response Time | P99 Response Time | QPS |
|---|---|---|---|
| Direct controller (traditional) | 18ms | 32ms | ~8,500 |
| Capa-BFF single service | 48ms | 72ms | ~3,200 |
| Capa-BFF three services aggregated | 150ms | 210ms | ~1,100 |
Yeah. That's a big difference. The dynamic DSL parsing, reflection-based composition, and runtime HTTP client handling all add overhead. Is it the end of the world? For internal tools, prototypes, hackathons, and non-critical paths — no. For your core high-traffic endpoint? Absolutely. You don't want this on your main payment processing API.
4. Async is more complex than it looks
Capa-BFF does support parallel calls to upstream services, which is great. But when one of them fails, what happens? The default behavior is to fail the whole request. That's what you want most of the time, but sometimes you want partial results. Handling that requires writing custom error handling in Java anyway, so you're back to writing code again.
5. Monitoring and observability are basic
Out of the box, you get some basic metrics — request count, latency, error count. But that's it. If you want distributed tracing across the upstream calls, you have to integrate it yourself. If you want to log the request/response payloads for debugging, you have to add that yourself. For production use, you end up writing a fair bit of infrastructure code that the framework doesn't give you.
The Honest Pros & Cons Breakdown
Let me make this clear for you — I like this project. It's clever, it solves a real problem, and it won us a hackathon. But it's not for every situation. Here's my honest breakdown:
✅ Pros
- Unbeatable for rapid prototyping and hackathons — seriously, nothing beats getting a working BFF in 10 minutes
- Perfect for internal tools and admin dashboards — where speed of development matters more than raw performance
- Great for small to medium projects — where you don't need a dedicated BFF team
- Zero cost — free as in beer and free as in speech
- Spring Boot friendly — fits right into your existing ecosystem
- Good documentation — the README is clear, and there are examples
❌ Cons
- No type safety — typecasts and typos bite you at runtime
- Performance overhead — not suitable for high-concurrency core paths
- Limited tooling — no IDE autocomplete, no refactoring support
- Basic observability — you'll need to add monitoring/tracing yourself
- DSL limitations — complex logic still requires writing Java
When Should You Actually Use It?
After three months of real-world use, here's my advice:
Use it when:
- You're building a prototype or hackathon project
- You need a quick BFF for an internal tool
- You're working on a side project or startup with limited time
- Your team already uses Spring Boot and you don't want to add a lot of infrastructure
- The endpoint isn't on your critical performance path
Don't use it when:
- You're building the main API for a high-traffic production service
- You have complex business logic in your BFF layer
- Your team values compile-time safety over development speed
- You need sophisticated monitoring and tracing out of the box
- You expect the endpoint to grow significantly in complexity over time
Honestly, I think the sweet spot is exactly what we used it for — hackathons and rapid prototyping. It excels there. Where it gets you in trouble is when you start using it for everything. "When all you have is a hammer, everything looks like a nail," right?
My Three Months in Production: What I Learned
We ended up putting our hackathon project into production after we won. It's been running for three months now, serving about 2,000 active users. Here's what I learned that you can't get from the README:
First, start simple, refactor when you need to. We started with all endpoints in Capa-BFF. As we've grown, we've moved the most frequently accessed endpoints into hand-written Java controllers. It took a couple of days, but the performance improvement was worth it. Capa-BFF gave us speed at the beginning, and we can always refactor later. That's a valid strategy, not a failure.
Second, the zero-code promise is a marketing trick. Don't get me wrong — it reduces code a lot. But you still need to understand BFF patterns, you still need to design your APIs well, and you'll still end up writing some Java for custom logic. "Zero code" doesn't mean "zero thought."
Third, trade-offs are everything. There's no such thing as a free lunch. Faster development now means you might pay for it later in performance and debuggability. That's okay if you make the choice consciously. The mistake is pretending the trade-offs don't exist.
Fourth, open-source projects like this need more contributors. Capa-BFF is a great idea, but it's maintained by a small team (actually, mostly one person from what I can see). If you use it and like it, consider contributing — whether it's documentation, bug fixes, or tooling like IDE plugins. We need more projects like this, but they need our support.
Wrapping Up: Is It Worth Using?
My answer is yes, but with your eyes open.
Capa-BFF didn't solve all our problems. It introduced new problems of its own. But it solved the problem we had at the time we had it. We needed to go fast, it let us go fast. We won a hackathon, we got our product to market, and we're still using it for most of our endpoints. When we outgrow it, we refactor. That's okay.
The software industry is obsessed with "best practices" and "the one right way" to do things. But honestly, I think the right way is whatever helps you ship working software now. Sometimes that means hand-writing every line of code. Sometimes that means using a zero-code framework that lets you ship in a weekend. Both are valid.
If you're in a hackathon or building a prototype, go try Capa-BFF. What's the worst that can happen? You learn something, and if it doesn't work out, you can always refactor later. That's what we did, and I don't regret it.
What's Your Experience?
I'm curious — have you ever used a zero-code or low-code framework for BFF or backend development? Did it work out for you, or did you end up rewriting everything? I'd love to hear your stories in the comments.
And if you've tried Capa-BFF specifically — what was your experience? Am I being too harsh, or do you agree with my take? Let me know!













