FTC Bonfire - Technology Stack
- Ronav Gupta

- 6 hours ago
- 13 min read
So people keep asking what we built this with, and honestly it's way more complex than it looks. This isn't some simple app thrown together in a weekend - we're running actual production infrastructure that handles real tournaments with hundreds of teams and thousands of users. Here's what's actually under the hood.
Architecture Overview
The technology stack diagram below shows how everything connects at a high level - what talks to what, and how data flows through the system. The detailed technology tables that follow explain what each piece actually does and why we chose it. Think of the diagram as the "big picture" and the tables as the "what's under the hood" details.

Frontend Web
The web console is built with React and TypeScript. It's a single-page application that talks to our backend API.
Technology | Description |
React & TypeScript | We use React 18 for the UI framework and TypeScript for type safety. TypeScript catches bugs before they happen and makes the codebase way easier to work with as it grows. |
React Router | This handles client-side routing. When you navigate between pages, it's all handled in the browser without full page reloads. Makes the app feel fast and responsive. |
Tailwind CSS | We use Tailwind for styling. Instead of writing custom CSS, we use utility classes. It's faster to build UIs and the final CSS bundle is smaller because unused styles get purged. |
React Hook Form & Zod | For forms, we use React Hook Form which handles form state and validation efficiently. We pair it with Zod for schema validation - define your validation rules once and they work on both client and server. |
Axios | This is our HTTP client for making API calls to the backend. It handles request/response interceptors (for adding auth tokens), error handling, and request cancellation. |
Recharts | This powers all the charts on the web console. OPR trends over time, team performance comparisons, phase breakdowns. It's interactive so you can hover to see exact values and zoom into specific time periods. |
React Leaflet | We use this for maps showing event locations and team geographic distribution. You can see where teams are located and how they're distributed across regions. |
React Simple Maps | For geographic visualizations of FTC team distribution across regions, we use this library. It renders SVG maps that are lightweight and interactive. |
CKEditor | For team notes and robot profiles, we use CKEditor. It's a rich text editor that lets you format text, add links, and include images. We also support markdown for people who prefer that. |
React Markdown | For rendering markdown content (like release notes, how-to guides), we use React Markdown. It converts markdown to React components. |
Mobile
We built native iOS and Android apps using Flutter. This lets us write one codebase that works on both platforms, but we still get native performance and can use platform-specific features.
Technology | Description |
Flutter & Dart | Flutter is Google's framework for building cross-platform apps. We write code in Dart and it compiles to native iOS and Android apps. One codebase, two platforms. We use Flutter Riverpod for state management, which is like React hooks but for Flutter. |
Go Router | This handles navigation in the mobile app. It's declarative routing so we can define routes and navigation flows in code, not just in config files. Makes deep linking and navigation state management way easier. |
SQLite (sqflite) | The mobile app stores everything locally in SQLite. This is what makes offline mode work - you can scout matches, view team data, and take notes even without internet. When you get back online, it syncs everything automatically. This was crucial because tournament venues often have terrible WiFi. |
Shared Preferences & Secure Storage | We use Shared Preferences for simple key-value storage (like user settings) and Flutter Secure Storage for sensitive data like auth tokens. Secure Storage encrypts data at rest so even if someone gets your phone, they can't just copy your session tokens. |
Dio | This is our HTTP client for making API calls. It's more powerful than the built-in HTTP client - handles timeouts, retries, interceptors for adding auth tokens, and request/response logging. We use it to talk to our backend API. |
Connectivity Plus | We check network connectivity before making API calls. If you're offline, we show cached data instead of trying to hit the API and failing. Simple but important for offline-first functionality. |
Google Sign-In | On mobile, you can sign in with your Google account. The Flutter plugin handles all the platform-specific stuff - on iOS it uses the native iOS Google Sign-In SDK, on Android it uses the Android SDK. We just call one function and it works on both. |
Apple Sign-In | On iOS, you can sign in with your Apple ID. Apple requires this if you offer social login. The Flutter plugin wraps Apple's native Sign In with Apple framework. |
Firebase Cloud Messaging | We use FCM for push notifications. When something important happens (match results, team updates), we send push notifications. Firebase handles all the platform-specific stuff - APNs for iOS, FCM for Android. We just send one notification and it works on both platforms. |
Local Notifications | We can show notifications even when the app is closed. Useful for reminders and alerts. We built this so it works offline too - you can set reminders that fire even without internet. |
Image Picker | For uploading scoreboard photos and team logos, we use the image picker. It lets you take a photo with the camera or pick one from your gallery. Works on both iOS and Android with one API. |
Cached Network Image | When we load images from the API (team logos, user avatars), we cache them locally. This means images load instantly on subsequent views and work offline. It handles all the caching logic for us. |
Fl Chart | This is our charting library for mobile. It's optimized for mobile performance and works smoothly even with lots of data points. We use it for OPR trends, team performance charts, and statistical visualizations. |
Flutter Form Builder | For complex forms (like scouting forms, event creation), we use Form Builder. It handles validation, error messages, and form state management. Way easier than building forms from scratch. |
Backend
The backend is a Spring Boot application running on AWS. It handles all the business logic, data processing, and integrations.
Framework & Core
Technology | Description |
Spring Boot | This is our main backend framework. It handles a lot of the boring infrastructure stuff - dependency injection, configuration management, embedded server. We use Spring Boot 3.5 with Java 17. |
Spring Data JPA | This is our ORM (Object-Relational Mapping). We define entities as Java classes and Spring Data JPA handles all the database queries. It generates SQL for us and handles relationships between tables. |
Hibernate | This is what Spring Data JPA uses under the hood. It handles the actual database interactions, connection pooling, and transaction management. |
Authentication & Security
Technology | Description |
JWT Tokens | When you log in, we give you tokens that prove who you are without storing your session on the server. This means the app works fast and we can scale to handle lots of users at once. We use access tokens for regular requests and refresh tokens that rotate to keep things secure. |
Google OAuth | Instead of making everyone create new accounts, you can sign in with your Google account. We use Spring Security OAuth2 Client which handles all the OAuth flow for us. |
Two-Factor Authentication | We added 2FA using TOTP (those six-digit codes from apps like Google Authenticator). You scan a QR code and then you need both your password and the code to log in. It's optional but recommended if you're a team captain or admin. |
Role-Based Permissions | Not everyone should be able to do everything. We have different permission levels - admins can do anything, team coaches can manage their team, regular members can view and scout. Spring Security checks your role before letting you do anything. |
BCrypt Password Hashing | We hash passwords with BCrypt before storing them. Even if someone got into our database, they couldn't see your actual password. And we have secure password reset flows that use tokens instead of just emailing you your password. |
Rate Limiting | We limit how many requests you can make to prevent people from abusing the API or accidentally crashing the server. We use Bucket4j with Redis to track rate limits across multiple server instances. |
Spring Security | This handles all the security stuff - authentication, authorization, CSRF protection, CORS configuration. It's a comprehensive security framework. |
Data Storage
Technology | Description |
Postgres SQL | This is our main database running on AWS RDS. It stores everything - teams, events, matches, scouting data, user accounts, team memberships. It's a relational database so we can do complex queries like "show me all teams from California that competed in events this season and have an OPR above 50." PostgreSQL handles transactions properly so if something fails halfway through, it rolls back instead of leaving bad data. |
Redis | This is our cache. It's super fast memory storage that we use for things that get accessed a lot but don't change often. Like if you search for a team, we cache the result so the next person searching for the same team gets instant results. We also use it for rate limiting and session storage. It's way faster than hitting PostgreSQL every time. |
Elasticsearch | This is our search engine. When you type in the team search box and see suggestions, that's Elasticsearch. It can search through millions of records in milliseconds. We index teams, events, and matches so you can find anything instantly. It's also what powers the filtered searches - like "show me all teams from Texas with OPR above 60 that competed in the last month." |
AWS S3 | We store all the images here - team logos, user avatars, scoreboard photos. S3 is basically unlimited storage that's super reliable. |
AI & Data Processing
Technology | Description |
OpenAI Integration | When you take a photo of a scoreboard, we send it to OpenAI's vision API. It reads the numbers and extracts all the match scores automatically. No more typing in scores manually during tournaments. We had to build a whole system to handle image uploads, send them to the API, parse the response, and handle errors gracefully. |
OPR/DPR/CCWM Calculations | These are the stats everyone cares about. We calculate them in real-time as matches finish. OPR tells you how many points a team contributes to their alliance on average, DPR is how many points they give up, and CCWM combines both. We use Apache Commons Math for the heavy statistical calculations because doing this stuff by hand would be way too slow. |
Alliance Compatibility Algorithm | This is probably the most complex thing we built. When you're picking alliance partners, we calculate a compatibility score based on multiple factors: do your OPR/DPR numbers complement each other, do you cover all the game phases, do your robot capabilities work well together, and have you won matches together before? It's not just averaging numbers - it's actually analyzing whether teams would work well together. |
Advancement Points | FTC has this complicated system for tracking who advances to championships. We automatically calculate all of it - qualification points from your ranking, points from alliance selection, playoff performance, and judged awards. It updates as tournaments progress. |
Real-Time Stats Engine | As matches finish, we recalculate everything. Team rankings update, leaderboards refresh, and all the phase-specific stats (autonomous, teleop, endgame) get recomputed. This happens automatically in the background so you always see current data. |
Real-Time Features
Technology | Description |
WebSockets | This is a persistent connection between your browser/app and the server. When a match finishes, the server pushes the update to everyone connected. No need to refresh or poll. We use Spring WebSocket for this. |
Server-Sent Events | Similar to WebSockets but one-way. We use this for notifications and match score updates. It's simpler and works better for things where you only need to receive updates, not send them. |
Conflict Resolution | This was tricky. When multiple people scout the same match at the same time, we need to merge their data intelligently. If two people both enter scores for the same match, we detect conflicts and resolve them automatically based on timestamps and confidence scores. |
APIs & Integrations
Technology | Description |
Spring Boot REST API | This is our main backend. It handles all the requests from the web and mobile apps. We use Spring Boot because it's mature, well-documented, and handles a lot of the boring infrastructure stuff for us. We have OpenAPI/Swagger docs so you can see all the endpoints and test them. |
FTC Events API | We sync with FIRST's official Events API to get event data, team registrations, and match schedules. This runs automatically on a schedule so we always have current data. We had to build retry logic and error handling because their API can be flaky sometimes. |
FTC Live API | During tournaments, we pull live match data from FTC's Live API. We cache it and relay it to our users with our own formatting and additional data. This lets us show live scores and stats even if FTC's website is slow. |
Email Service | We use AWS SES to send emails for team invitations, password resets, and notifications. It's way more reliable than trying to send emails directly from our server. |
Spring WebFlux | When we call external APIs, we use Spring WebFlux which is non-blocking. This means if an API call takes a while, it doesn't freeze up the server. We can handle way more concurrent requests this way. |
Firebase Admin SDK | We use Firebase Admin SDK to send push notifications to mobile devices. When something important happens, we send notifications through Firebase Cloud Messaging. |
Infrastructure & Deployment
Technology | Description |
AWS ECS | Our backend runs in Docker containers on AWS ECS. ECS manages multiple instances of our app, distributes traffic between them, and automatically restarts containers if they crash. We can scale up during big tournaments and scale down when it's quiet. |
AWS Application Load Balancer | This sits in front of our servers and distributes incoming requests across multiple instances. If one server goes down, traffic automatically routes to the others. It also handles SSL termination so we don't have to manage certificates ourselves. |
AWS RDS PostgreSQL | Our main database runs on AWS RDS, which is managed PostgreSQL. AWS handles backups, updates, and scaling. We don't have to manage the database server ourselves. |
AWS ElastiCache Redis | Our Redis cache runs on AWS ElastiCache. It's a managed service so we don't have to worry about keeping Redis servers running. |
AWS OpenSearch | Our Elasticsearch cluster runs on AWS OpenSearch Service. It's managed Elasticsearch that scales automatically as we index more data. |
Docker | Everything runs in containers. This means it works the same way on my laptop, in testing, and in production. No more "it works on my machine" problems. |
Kubernetes | We have Kubernetes configs ready if we want to switch from ECS. It's more complex but gives us more control over how things run. |
DevOps & Monitoring
Technology | Description |
GitHub Actions | Our CI/CD pipeline runs automatically. When we push code, it runs tests, builds Docker images, scans for security vulnerabilities, and can deploy to production. This means we can deploy multiple times a day without manually doing a bunch of steps. |
Automated Testing | We run tests automatically on every commit. If tests fail, the build fails and we can't deploy broken code. We use JUnit for backend tests and have integration tests that spin up real databases using TestContainers. |
Security Scanning | We use Trivy to scan our Docker images for known vulnerabilities. If something insecure gets into our code, we know about it before it hits production. |
Spring Boot Actuator | This gives us health check endpoints. ECS uses these to know if our app is healthy and should receive traffic. If health checks fail, ECS restarts the container. |
AWS CloudWatch | This collects all our logs and metrics. We can see what's happening in real-time, debug issues, and get alerts if something goes wrong. It's like having a dashboard showing everything about our app. |
AWS Secrets Manager | We store all sensitive stuff (database passwords, API keys, OAuth secrets) in AWS Secrets Manager. The app pulls them at runtime. This is way safer than hardcoding secrets in the code or storing them in config files. |
ShedLock | We run scheduled jobs (like calculating OPRs or syncing FTC data) that need to run once across multiple server instances. ShedLock ensures only one instance runs each job, even if we have multiple servers. |
Developer Tools
These are the tools we use to build, test, and deploy everything. They're not part of the application itself, but we couldn't build it without them.
Tool | Description |
IntelliJ IDEA | This is what we use for Java backend development. It has amazing code completion, refactoring tools, and debugging. Spring Boot support is built-in so it understands our code structure and can help with common patterns. |
Cursor | We use Cursor (which is based on VS Code) for frontend development. It's great for React/TypeScript and has AI coding assistance built in. We also use it for general file editing and working with config files. |
Xcode | For iOS development, we use Xcode. It handles iOS builds, code signing, and App Store submissions. You need a Mac to use it, but it's the only way to build iOS apps. |
Android Studio | For Android development, we use Android Studio. It handles Android builds, signing, and Play Store submissions. It's based on IntelliJ IDEA so it has similar features. |
Docker Desktop | For local development, we run everything in Docker containers. Docker Desktop lets us run PostgreSQL, Redis, and other services locally without installing them directly on our machines. We can spin up the entire stack with docker-compose and it matches production. |
Postman / Insomnia | For testing API endpoints, we use Postman or Insomnia (which is an open-source API testing tool similar to Postman, some people prefer it for the cleaner interface). You can't build a REST API without being able to test it. We also have OpenAPI/Swagger docs that generate interactive API docs you can test from. |
Git | Obviously we use Git for version control. We use GitHub for hosting repos and managing pull requests. Every feature gets its own branch and we review code before merging. |
Node.js / npm | For frontend development, we need Node.js to run the React app locally. Vite runs a dev server that hot-reloads when we change code. |
Vite | This is our build tool and dev server for the frontend. It's way faster than Create React App - hot module replacement is instant and builds are quick. We use it for both development and production builds. |
Java 17 JDK | For backend development, we need Java 17. |
Maven | This is our build tool and dependency manager for the backend. It downloads all the libraries we need and builds the application into a JAR file. |
Flyway | When we need to change the database structure (like adding a new column), we write migration scripts. Flyway runs them automatically and keeps track of what's been applied. This means our database structure is version controlled and consistent across development, staging, and production. |
Freezed & JSON Serializable | These are code generation tools for Flutter. Freezed generates immutable data classes (like our API models) and JSON Serializable generates JSON serialization code. This means we write less boilerplate and get type-safe models. |
Why This Matters
The reason we built it this way instead of using something simple:
Multiple Databases - PostgreSQL for reliable data storage, Redis for fast caching, Elasticsearch for instant search. Each one is optimized for what it does. Using just one database would be slow and expensive.
Non-Blocking Operations - We use async/reactive patterns so the server doesn't freeze up under load. If one request is slow, it doesn't block other requests. This is how we handle thousands of concurrent users during tournaments.
Distributed Task Scheduling - We run scheduled jobs (like calculating OPRs or syncing FTC data) that need to run once across multiple server instances. We use ShedLock to ensure only one instance runs each job, even if we have multiple servers.
Connection Pooling - We use HikariCP to manage database connections efficiently. It detects connection leaks, manages timeouts, and reuses connections instead of creating new ones constantly. This prevents the database from getting overwhelmed.
Database Migrations - All database changes are version controlled and applied automatically. This means our database structure is consistent everywhere and we can roll back if something breaks.
Offline-First Mobile - The mobile app works completely offline because tournaments often have terrible WiFi. Everything syncs when you get back online. This required building a whole sync system that handles conflicts and errors gracefully.
Multi-Platform - We have a web console (React), iOS app (Flutter), and Android app (Flutter), all talking to the same backend API. This means features work everywhere and we only have to build the backend logic once.
Production Monitoring - We have health checks, metrics, logging, and error tracking everywhere. When something breaks, we know immediately and can fix it fast.



Comments