My Hackathon Submission (And a Review of Your Tools)

The Introduction

I imagine hackathon judges have it easy. They get to sip coffee in a high-rise office, snobbishly looking over project demos from contestants. When one catches their eye, they dive into the repository to scrutinize the code, digging until a project stands out, and then another. Now, they get to pit them against each other. Scrutinizing the code doesn't even have to be manual; you can just have AI do the grunt work. Pretty easy stuff, it seems.

Well, today, I'm turning the tables. I’ll be the one judging the partner integrations for this hackathon, based on their ease of use and the developer experience they offered. For this, I built Chat (or Babel Chat?, more on that later), a real-time chat application that uses AI to translate conversations on the fly, making language barriers a thing of the past.

This is the story of how I built it: the technical issues I encountered, the tools that could use some improvement, and which ones made me question my life choices. And ultimately, it’s about why this project shows I'm ready to build amazing things with your team. (Yes, I am looking for work!)

The Inspiration

Every good project starts with a spark. Mine came from a 450-year-old painting: Pieter Bruegel the Elder's The Tower of Babel.

My day began like any other, scrolling through my inbox, reading newsletters, and scanning the Google Culture Weekly when I stumbled upon it. The story of Babel is a familiar one: a cautionary tale of how humanity’s shared tongue and ambition were scattered as a punishment for their ingenuity. A thought stuck with me: if we, as a people, truly unite to accomplish something, we become so unstoppable that it would take divine intervention to break us apart.

But I digress.

While this idea was still turning over in my mind, I opened Twitter, and the first post I saw was Convex announcing their hackathon. It turned out I was already a few days late. With nothing else on my plate (if it isn't clear enough yet, I'm open to work), I decided to give Convex a shot.

Looking at Bruegel's chaotic, ambitious, and ultimately failed project, I saw a parallel to the modern debate over remote work. Many companies push for on-site employees, cutting out so much talent for fear that communication will break down, that our languages will become scattered. I decided to write the code to bring them back together.

Why use AI for translation instead of a dedicated API? For context. Context is the critical, often-missed element in conversations between languages. With a good system prompt and the last few messages as a guide, AI becomes a tone-sensitive interpreter, not just a literal translator.

This is what I love to do: turn abstract ideas from art into functional code. If you need someone who can bridge the gap between vision and execution, my contact info is at the end.

The Integrations

Here's my very opinionated report card on the partner tools for this hackathon. Most are excellent, to say the least, starting with TanStack Start and going all the way down to CodeRabbit.

TanStack Start

Almost everyone in the front-end ecosystem knows about projects by Tanner Linsley. The TanStack ecosystem offers high-quality, open-source software, and I've used TanStack Query for a long time. I love the beauty of it, the way it handles caching, requests, retries, errors, and optimistic updates. When I want to start a new side project, I always default to React Router (in framework mode), and I’ve been watching TanStack Start since its alpha.

I’m not one to jump on the latest tech right away. Even though I trust anything coming from the TanStack ecosystem, I held off because I had work to do. Now, for the framework itself: I’ve used Next.js and enjoyed its use of directives to separate server and client code. It had the best DX for beginners, why think about complexity when you can just add a string to the top of a file and move on? Its RPC-like Server Actions were fun, too.

But that fun lasted only until the project grew to a considerable size. The dev server would grind to a halt, the cache turned into some kind of dark magic, Vercel costs skyrocketed, and self-hosting meant digging through the internals of Next.js. So, I switched back to React Router, and eventually, React Router (framework mode) became my default, especially since its ease of deployment to any platform was a huge win. While newer Next.js versions claim to have fixed these issues, I've already jumped ship.

TanStack Start incorporates all the features I like about Next.js and the features I love about React Router, then slaps type-safe routes on as the cherry on top. The DX is perfect. Using Vite as a bundler makes it extremely easy to deploy across platforms, the dev server is light, it’s easy to get up and running, and it has a reasonable middleware story.

The struggle I had, though, was with the documentation, which feels like a brain dump. One thing the Vercel team has going for Next.js is that its documentation is always well-organized and put together. For instance, trying to set up dark and light mode for this project had me searching all over for documentation on cookies. I ended up with a nifty solution:

	
	import { createServerFn } from "@tanstack/react-start";
import { getCookie, setCookie } from "@tanstack/react-start/server";
import * as z from "zod";

const postThemeValidator = z.union([z.literal("light"), z.literal("dark")]);

export type T = z.infer<typeof postThemeValidator>;
const storageKey = "_preferred-theme";

export const getThemeServerFn = createServerFn().handler(async () => (getCookie(storageKey) || "dark") as T,
);

export const setThemeServerFn = createServerFn({ method: "POST" })
.inputValidator(postThemeValidator)
.handler(async ({ data }) => setCookie(storageKey, data));

Another example was the internationalization I wanted to implement. According to the docs, the best approach was using optional path params, but there were hardly any examples showing how it's done. What I do like about the docs, though, are the embedded StackBlitz containers. I used them a lot to experiment and copy code into my project.

My verdict: TanStack Start is the most promising front-end-first, full-stack framework to come out of the JavaScript ecosystem. Couple that with the entire TanStack ecosystem, and you have a Swiss Army knife of best-in-class DX and tools. Grade: A. The documentation needs work.

Convex

When Convex first launched, I was intrigued. The problems it promised to solve were the very ones I have to battle regularly. As a back-end-first full-stack developer (in case you're hiring), I'm constantly dealing with issues far beyond basic CRUD.

Now, down to Convex. Working with it is just plain fun. I finally understand why Theo jumped ship to build his back-end with it and hasn't looked back. The moment you run npx convex, you get a fully functional back-end framework, complete with functions, a database, cron jobs, and a wonderful sync engine.

I am so fascinated by Convex's sync engine that it's on my to-do list to understand how they handle writes, how the local environment syncs so quickly with the dashboard, and their overall strategy. As an end-user, it's exciting to try and grasp what's going on under the hood because it's such a well-thought-out and well-executed project. The developer experience was amazing; everything is type-safe and instant. It was a delight to work with. The documentation is comprehensive, and anything I couldn't find on the main docs I found on their Stack website.

After the initial setup, I started implementing the project. It's incredibly fast, and one thing that really stands out is how changes to the convex/ folder are automatically compiled and deployed to the dev instance. This ensures consistency and lets you test your functions exactly as they would run in production.

I decided to test everything meticulously. Queries and mutations were a no-brainer, so I started thinking about other things a typical chat app back-end would need. I started with authentication and authorization. Row-level security was as simple as writing:

	
	import {customCtx, customMutation, customQuery} from "convex-helpers/server/customFunctions";
import {type Rules, wrapDatabaseReader, wrapDatabaseWriter} from "convex-helpers/server/rowLevelSecurity";
import type { DataModel, Doc } from "./_generated/dataModel";
import { mutation, type QueryCtx, query } from "./_generated/server";

export const rules = async (ctx: QueryCtx) => {
    // your rules
}

export const protectedQuery = customQuery(query, customCtx(async (ctx) => {
  const identity = await ctx.auth.getUserIdentity()
  if (!identity) {
    throw new Error('Not authenticated')
  }
  const user = await ctx.db.query("users").withIndex("by_token", (q) => q.eq("tokenIdentifier", identity.tokenIdentifier)).unique();

  if (!user) {
    throw new Error("User not found");
  }

  const db = wrapDatabaseReader({ user }, ctx.db, await rules(ctx));

  return { user, db };
}))

export const protectedMutation = customMutation(mutation, customCtx(async (ctx) => {
// basically the same thing with the first one above
}))

Not only is it easy, but it's so well done it works like magic. It was so effective that I accidentally wrote a bug where a user could send a message but couldn't modify a room, only the admin could. It was a facepalm moment when I realized the room rule was clashing with the message-writing permissions. It was an easy fix.

Next up was handling third-party APIs. The docs made it seem like this might be hard to monitor. Thinking back, consistency is a core part of their architecture, and when integrating third-party APIs, anything can happen, errors, malformed responses, all of which could break Convex's strong consistency guarantees. But with actions, it was a breeze to set up.

Naturally, the next step was queuing API requests. There’s a built-in scheduler for that: ctx.runAt(timestamp, api.whatever.the.function, {args}). Then you need a way to retry those calls. You're in luck. Convex has something they call components, modules you can attach to your project, and one of them is an Action Retrier that can be combined with mutations, actions, and cron jobs. The documentation was comprehensive, too.

These are the essentials, but what about things that would make my chat app even better? Something like showing when users are online or typing. Boom, another component for that: convex-presence. The implementation and documentation were superb.

The front-end is real-time, so I thought adding a separate caching layer would be overkill. I initially planned to save messages in IndexedDB while the back-end function sent the message, translated it, and returned the output. I realized I didn't need that, Convex was just that fast. Optimistic updates were a no-brainer.

One thing, though, was the integration with TanStack Query. While it’s currently in beta, it would be really nice if TanStack's useInfiniteQuery played nicely with Convex's paginated queries.

Speaking of which, I'm an expert at leveraging powerful platforms like Convex to build scalable, secure, and blazingly fast applications. You can email me at owen@efobi.dev.

My verdict: The team behind Convex has put out an excellent platform. It is excellently documented, with ready-to-use features, components, and use cases. It's a beautiful piece of technology and a feature-complete back-end, much like what's available on Rails. Grade: A+

Cloudflare

Cloudflare is my go-to for CDNs, and I have nothing but praise for the platform. I’ve been using them since the early days of Cloudflare Pages. I have a personal story with them: I was hosting a few Lambda functions on AWS for some personal scripts, and while it wasn't a lot, I was still incurring monthly costs. I was paying $12 a month for functions that ran for only a few seconds each day. I slowly migrated them all to Cloudflare Workers, and now, every time that $0 invoice comes in, I can't help but smile.

That aside, I'm on their developer plan, paying $5 a month for some of my side projects built with React Router, integrating their D1, R2, Durable Objects, Turnstile, and recently Hyperdrive. I don't mind vendor lock-in when it's mostly free. If anything, I can just upgrade my plan or refactor away, since there’s barely any lock-in with React Router.

I’m a strong advocate for Cloudflare and have long been an avid reader of their articles on handling internet infrastructure and security at scale. Here are some of my favorites:

While it’s nice that they're finally focusing on DX and improving Wrangler, there’s still work to do. For one, the local workerd environment doesn't always match the production environment, which can be a pain to debug.

I also noticed a couple of bugs while building this application. First, when building locally, the @cloudflare/vite-plugin for TanStack Start prioritizes .env.local over .env. If you have both in your project, the plugin rewrites the contents of .env.local into dist/server/.dev.vars during the build process. Second, and I suspect it's related, is an issue with the Workers' Git integration. When you connect a Git repository from the dashboard, the build will succeed, but since you obviously aren't committing your environment variables, none will be set. Even if you add them manually in the dashboard, they don't seem to apply. This was my experience working on this project.

My verdict: The Cloudflare developer platform is excellent and feature-rich, with powerful tools like their CDN, D1, Workers, Durable Objects, Turnstile, and Hyperdrive. However, there's still room for improvement in the developer experience and documentation. Grade: B

Sentry

This was my second time using Sentry, and on both occasions, it was excellent to work with. The first was for a startup that needed to integrate error reporting and session replay into their web app. For this project, adding Sentry to the front-end was as simple as looking over the docs and pasting in the integration code for TanStack Start. The Sentry integration with Convex is behind a paywall, so I don't have much to say about the back-end experience.

It was incredibly easy to set up error reporting, session replay, and user feedback. Sentry, as a company, is one I’m rooting for because of their open-source licensing and their FSL License. It's rare to find a company that is open source from the ground up, rather than one born from forking an existing project.

The Sentry SDK was a breeze to set up. While I didn't implement every feature it offers, the parts I did use were very straightforward. I would advise new developers to start with Sentry, as not many learn about error reporting and debugging beyond console.log. The ones who go a step further usually just end up at console.error, without ever touching telemetry or performance monitoring.

That aside, if you're hiring and need a developer with industry experience in these things, I'm the right guy for the job.

My verdict: Sentry, its SDK and its features, just works. It was a breeze to set up, the revamped dashboard is insightful and easy to use, and the SDK's availability for different frameworks is well done. Extra bonus points for the open-source licensing and ease of distribution. Grade: A.

Autumn

I'll root for any abstraction that can clean up Stripe's webhook mess. That said, integrating Autumn was extremely easy because, of course, the perfect golden child Convex already has a component for it.

All I had to do was copy the necessary code from the docs. It was a breeze to set up, except for one small error I ran into in the documentation. It tells you to import autumn from convex/autumn, but no such file is generated or exists. I spent a good 20 minutes debugging that, mostly because I’d gotten so used to Convex and its docs just working. Funny enough, the Autumn docs give the exact same recommendation, so maybe I missed something.

In the end, I got it working by importing autumn from the convex/autumn.ts file I had to define myself, rather than what was suggested:

	
	import {autumn} from './autumn'

const {data, error} = await autumn.check(ctx, {
  featureId: 'messages'
})

It was incredibly easy. The code above is all you need to check a user's usage, and autumn.track() is all you need to track it. Seeing all the messy, annoying-to-debug parts of Stripe simplified into a few clean functions made me smile, especially after being frustrated by Stripe billing in the past.

I did notice a couple of spots where type safety could be improved. For instance, why set ctx to any and lose all that type safety...

	
	import {Autumn} from '@useautumn/convex'

export const autumn = new Autumn(components.autumn, {
  secretKey: process.env.AUTUMN_SECRET_KEY ?? '',
    // in the docs ctx was typed as any
  identify: async (ctx: any) => {
    //...
  }
})

...when you could do this instead?

	
	import {Autumn} from '@useautumn/convex'
import {components} from './_generated/api'
import type {GenericCtx} from './_generated/server'

export const autumn = new Autumn(components.autumn, {
  secretKey: process.env.AUTUMN_SECRET_KEY ?? '',
  identify: async (ctx: GenericCtx) => {
    const user = await ctx.auth.getUserIdentity()
    // from here on out, ctx and the function will be type-safe
  }
})

Then, on the front-end, the AutumnProvider expects the convex and convexApi props to be any, so you lose type safety there as well. You can pass in anything, and TypeScript won't catch it:

	
	import {useConvex} from 'convex/react'
import {api} from '../../_generated/api'

function RootComponent() {
  // this always returns a type of ConvexReactClient
  const convex = useConvex()

  return (
  // this isn't type-safe as both props expect `any`
    <AutumnProvider convex={convex} convexApi={api}>
      {children}
    </AutumnProvider>
  )
}

As you may have noticed, I'm a meticulous developer who finds ways around problems, a skill that would be incredibly useful on your team.

My verdict: Any abstraction over Stripe is already a win, and this one is so well done, simplifying billing, plan upgrades, and usage monitoring into simple components. There are just a few minor inconsistencies in the docs and some gaps in type safety. Grade: B.

CodeRabbit, Firecrawl, and Netlify

I didn't end up using every partner tool for this project. I was already set on using Cloudflare for deployments, and finding a use case for Firecrawl, an AI-first web scraper, felt like a stretch for a chat app.

I was curious about CodeRabbit, though. I'm generally skeptical of AI reviewing my code, or writing it, for that matter, but I decided to give it a shot. I installed the VS Code extension and intentionally committed an error to see if it would catch it. It did, and even suggested a fix I could auto-apply. I had a function returning nothing where it shouldn't have, and it flagged it perfectly.

in-article-image


However, it also incorrectly claimed I was using a version of Sentry that doesn't exist, suggesting I downgrade the package to one from before its knowledge cutoff. So yeah, it’s good, but not quite good enough to be a daily driver.

The Intermission

While trying to stick with my "The Int..." theme, a quick Google search led me to "intermission", a break between parts of a performance. This feels fitting. Let's call this the conclusion to Part One. Part Two, hopefully, is where I'm working for your company.

I'm looking for work and would love to talk if you're hiring.

What happens next?

If I were to keep building this out, the logical next steps would be to add:

  • Voice-to-text with translation: This would be a perfect use case for adding ElevenLabs, another product I'd really like to play with.
  • Tone analysis with icons: Imagine using emojis to tell you if that message from your boss is "curious" or "furious."
  • Chat notifications: Alerts for when you're added to a room or when a new message comes in.
  • Automatic language detection: This one's a no-brainer. Instead of having to set the language manually, the AI could infer it and translate accordingly.

This hackathon was a blast. The tech stack is incredibly powerful and proves that there has never been a better time to be a developer, we can build truly powerful, production-ready applications in a single weekend.

And if you're a hiring manager, engineering lead, or recruiter who has made it this far, thank you. I'm passionate, a fast learner, and ready for my next challenge as a full-stack software engineer.

Let's build something amazing together.

  1. Check out the live app: Chat (or Babel Chat?)
  2. Explore the code: GitHub
  3. Connect with me: LinkedIn
  4. You're here: My Portfolio

Connect with us.

We're a remote-first design studio partnering with teams across every time zone. Let's build something amazing together.

Loading...

Start a conversation

Tell us about your project and we'll get back to you within 24 hours.

Or email us at owen@efobi.dev