How to Build a Sports Betting App
The full stack for a sports betting app — from picking an API to displaying real-time lines, to detecting value, to shipping to production. All examples use SharpAPI (free tier available).
To build a sports betting app you need an odds API, a frontend framework, and a deployment platform. The fastest path: Next.js + SharpAPI + Vercel. Fetch odds through a server route, render them in a React component, and upgrade to SSE streaming for real-time line updates. Built-in arbitrage and +EV endpoints remove weeks of algorithm work — ship a working odds comparison in a day and a full arbitrage scanner in a week.
1. Choose What You're Building
| App Type | Complexity | API Plan Needed | Monetization |
|---|---|---|---|
| Odds comparison | Low | Free | Affiliate links |
| Arbitrage scanner | Medium | Hobby ($79/mo) | Subscription |
| +EV tool | Medium | Pro ($229/mo) | Subscription |
| Line movement tracker | Medium | Hobby | Subscription |
| Discord/Telegram bot | Low | Free / Hobby | Subscription |
| DFS research tool | Medium | Free | Subscription |
| Full analytics platform | High | Pro / Sharp | Subscription |
This guide walks through an odds comparison app as the baseline, then shows how to extend it for arbitrage and +EV.
2. Project Setup
# Next.js app (React frontend + API routes)
npx create-next-app@latest my-betting-app --typescript --tailwind
cd my-betting-app
# SharpAPI SDK
npm install @sharp-api/client
# Environment
echo "SHARPAPI_KEY=your_key_here" >> .env.local3. Fetch and Display Odds (Backend Route)
app/api/odds/route.ts
import SharpAPI from '@sharp-api/client'
import { NextResponse } from 'next/server'
const client = new SharpAPI({ apiKey: process.env.SHARPAPI_KEY! })
export async function GET(req: Request) {
const { searchParams } = new URL(req.url)
const sport = searchParams.get('sport') || 'basketball_nba'
const odds = await client.odds.list({
sport,
markets: ['h2h', 'spreads', 'totals'],
})
return NextResponse.json(odds)
}4. Display Best Lines per Team (React Component)
components/OddsTable.tsx
'use client'
import { useEffect, useState } from 'react'
interface Outcome { name: string; price: number; sportsbook: string }
interface Event {
id: string
homeTeam: string
awayTeam: string
commenceTime: string
bestOdds: Record<string, Outcome>
}
export function OddsTable({ sport }: { sport: string }) {
const [events, setEvents] = useState<Event[]>([])
useEffect(() => {
fetch(`/api/odds?sport=${sport}`)
.then(r => r.json())
.then(data => {
const processed = data.data.map((event: any) => {
const bestOdds: Record<string, Outcome> = {}
for (const book of event.bookmakers) {
for (const market of book.markets) {
if (market.key !== 'h2h') continue
for (const outcome of market.outcomes) {
if (!bestOdds[outcome.name]
|| outcome.price > bestOdds[outcome.name].price) {
bestOdds[outcome.name] = {
name: outcome.name,
price: outcome.price,
sportsbook: book.title,
}
}
}
}
}
return {
id: event.id,
homeTeam: event.homeTeam,
awayTeam: event.awayTeam,
commenceTime: event.commenceTime,
bestOdds,
}
})
setEvents(processed)
})
}, [sport])
return (
<table className="w-full text-sm">
<thead>
<tr className="border-b border-zinc-700">
<th className="py-2 text-left">Game</th>
<th className="py-2 text-right">Best Home</th>
<th className="py-2 text-right">Best Away</th>
</tr>
</thead>
<tbody>
{events.map(event => (
<tr key={event.id} className="border-b border-zinc-800">
<td className="py-3">{event.homeTeam} vs {event.awayTeam}</td>
<td className="py-3 text-right text-emerald-400">
{event.bestOdds[event.homeTeam]?.price}
<span className="ml-1 text-zinc-500 text-xs">
{event.bestOdds[event.homeTeam]?.sportsbook}
</span>
</td>
<td className="py-3 text-right text-emerald-400">
{event.bestOdds[event.awayTeam]?.price}
<span className="ml-1 text-zinc-500 text-xs">
{event.bestOdds[event.awayTeam]?.sportsbook}
</span>
</td>
</tr>
))}
</tbody>
</table>
)
}5. Add Real-Time Updates with SSE
Instead of polling, stream updates directly to the browser (Hobby plan or above):
app/api/stream/route.ts
import SharpAPI from '@sharp-api/client'
const client = new SharpAPI({ apiKey: process.env.SHARPAPI_KEY! })
export async function GET() {
const stream = new ReadableStream({
async start(controller) {
const encoder = new TextEncoder()
for await (const update of client.stream.odds({
sports: ['basketball_nba'],
})) {
controller.enqueue(
encoder.encode(`data: ${JSON.stringify(update)}\n\n`),
)
}
},
})
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
})
}Client-side hook:
useEffect(() => {
const es = new EventSource('/api/stream')
es.onmessage = (e) => {
const update = JSON.parse(e.data)
// Update your odds state with delta
applyOddsDelta(update)
}
return () => es.close()
}, [])6. Add Arbitrage Detection (Hobby+)
Use SharpAPI's built-in arb endpoint instead of writing your own:
// app/api/arb/route.ts
const arb = await client.opportunities.arbitrage({ sport: 'basketball_nba' })
// Each opportunity has profit %, legs, and stake recommendations
return NextResponse.json(arb)Arb alert component:
{arbOpps.map(opp => (
<div
key={opp.id}
className="rounded-lg border border-emerald-500/20 bg-emerald-500/5 p-4"
>
<div className="font-bold text-emerald-400">
{opp.profitPercent.toFixed(2)}% Arbitrage
</div>
<div className="text-sm text-zinc-400">{opp.event}</div>
{opp.legs.map(leg => (
<div key={leg.outcome} className="mt-1 text-sm">
{leg.outcome} @ {leg.price > 0 ? '+' : ''}{leg.price}
{' '}({leg.sportsbook}) — stake {leg.stakePercent.toFixed(1)}%
</div>
))}
</div>
))}See: Arbitrage Betting API and Sports Betting Arbitrage Explained.
7. Deploy to Vercel
vercel --prod
# Set env var in Vercel dashboard or CLI:
vercel env add SHARPAPI_KEY productionAdd ISR revalidation for sport pages so stale data isn't served:
// In your odds API route
export const revalidate = 30 // ISR every 30 seconds8. Monetization Options
Affiliate revenue
Link to sportsbooks with affiliate links when displaying their odds. DraftKings, FanDuel, and BetMGM all have affiliate programs paying per new deposit.
Subscription
Gate premium features (arb alerts, +EV finder, streaming) behind a paid tier using Stripe or Paddle.
API resale
Build on top of SharpAPI and offer your own API with custom features — Discord integrations, alert thresholds, team filters, etc.