TL;DR: I built an open-source billiard hall management system with Next.js 15, React 19, and PostgreSQL. It handles everything from table sessions to F&B orders, payments, and analytics. Try it on Railway or run it with Docker.
The Problem
Running a billiard hall in Indonesia involves juggling multiple systems: table time tracking, F&B orders, payments, staff management, and inventory. Most solutions are either:
- Expensive SaaS with monthly fees
- Excel spreadsheets (yes, really)
- Custom solutions that can't be easily replicated
I wanted to build something that any billiard hall could deploy and own their data, whether they're in Jakarta, Manila, or anywhere else.
Why Open Source?
At Kugie, our motto is "Scale Smarter, Not Harder." For small businesses, that means:
- No vendor lock-in - Your data stays yours
- Deploy anywhere - Railway, Docker, VPS, or even Windows standalone
- Customize freely - Fork it and make it yours
- Community-driven - Features that actual operators need
The Tech Stack
// The modern stack that just works
- Next.js 15 (App Router + React 19)
- TypeScript (because types save lives)
- Drizzle ORM + PostgreSQL
- Tailwind + Shadcn/ui
- NextAuth.js for authentication
- Bun for speed
Why These Choices?
Next.js 15 with App Router: Server components give us fast initial loads - crucial for operators checking tables on slower Indonesian internet.
Drizzle ORM: After dealing with Prisma's bulk query limitations at scale, Drizzle's SQL-like syntax and better performance won me over. Plus, Drizzle Studio is fantastic for database debugging.
PostgreSQL: Battle-tested, great JSON support for flexible F&B item properties, and works everywhere - from Neon serverless to local Docker.
Key Features I'm Proud Of
1. Context-Aware F&B Orders
Orders can be:
- Linked to table sessions
- Standalone counter orders
- Draft orders (for customers waiting for tables)
// The schema handles all three contexts elegantly
export const fnbOrders = pgTable("fnb_orders", {
tableSessionId: uuid("table_session_id").references(() => tableSessions.id),
paymentId: uuid("payment_id").references(() => payments.id),
status: varchar("status", { length: 20 }).notNull().default("draft"),
// draft → pending → completed
});
2. Flexible Deployment Options
| Method | Best For | Setup Time |
|---|---|---|
| Railway | Cloud, zero config | 2 minutes |
| Docker | Self-hosted VPS | 5 minutes |
| Windows Standalone | Local with auto-update | 10 minutes |
3. Real-Time Analytics Without the Overhead
Pre-calculated analytics stored in order_analytics table:
- Revenue by hour/day/month
- Popular items and peak times
- Staff performance tracking
No need for expensive analytics services - just PostgreSQL doing what it does best.
Challenges & Solutions
Challenge 1: Supporting Poor Internet Connectivity
Problem: Many billiard halls in Indonesia have unreliable internet.
Solution:
- Optimistic UI updates with local state
- Service Worker for offline capability (planned)
- Windows standalone that works 100% locally
Challenge 2: Multi-Language Support
Problem: Staff might prefer Indonesian, but owners want English reports.
Solution: next-intl with route-based locales (/id/dashboard vs /en/dashboard)
// Clean separation of concerns
messages/
├── id/
│ ├── common.json
│ ├── dashboard.json
│ └── fnb.json
└── en/
├── common.json
├── dashboard.json
└── fnb.json
Challenge 3: Complex Payment Flows
Problem: A single payment might include:
- Multiple table sessions
- Multiple F&B orders
- Split payments
- Tips
Solution: Consolidated payment model with JSON metadata:
export const payments = pgTable("payments", {
totalAmount: numeric("total_amount", { precision: 10, scale: 2 }).notNull(),
metadata: json("metadata"), // Flexible structure for complex scenarios
paymentMethod: varchar("payment_method", { length: 50 }).notNull(),
});
What's Next?
I'm preparing to launch Chalkboard v1.0.3 widely. Planned features:
- Mobile PWA for table-side ordering
- Multi-location support for chains
- Advanced inventory with supplier management
- Membership system with loyalty points
Try It Yourself
Quick deploy:
Feedback welcome! Whether you run a billiard hall, arcade, or any time-based rental business, I'd love to hear if this could work for you.
Top comments (0)