QR Invite: Event Management with Go and QR Codes
GoPostgreSQLQR CodeWhatsApp APIJWT
QR Invite: Event Management with Go and QR Codes
QR Invite is a backend system for managing event invitations through QR codes. Guests receive invites via WhatsApp or email, view their invite with a generated QR code, and admins scan the QR code at the door to mark attendance.
Flow
- Admin adds participants — CSV upload or individual entry via admin dashboard
- Admin sends invites — Bulk or individual invites via WhatsApp (Meta Graph API) or email (Resend)
- Guest receives invite — WhatsApp message or email with a unique link containing their
external_id - Guest opens link — Sees their invite details and QR code
- Admin scans QR code at door — Marks participant as attended
- Admin monitors — Real-time dashboard showing check-in status
Architecture
┌──────────────────────────────────────────────────────────┐
│ HTTP Server │
│ ┌──────────┐ ┌────────────┐ ┌──────────┐ ┌────────┐ │
│ │ Public │ │ Auth │ │ Admin │ │ Invite │ │
│ │ Handlers │ │ Handlers │ │ Handlers │ │Handlers│ │
│ └────┬─────┘ └─────┬──────┘ └────┬─────┘ └───┬────┘ │
│ │ │ │ │ │
│ ┌────┴─────┐ ┌─────┴──────┐ ┌────┴─────┐ ┌───┴────┐ │
│ │ Public │ │ JwtService │ │ admin │ │ Invite │ │
│ │ Service │ │ │ │ Store │ │ Service│ │
│ └────┬─────┘ └────────────┘ └────┬─────┘ └───┬────┘ │
│ └──────────────┬──────────────┴────────────┘ │
│ ▼ │
│ ┌──────────────┐ │
│ │ PostgreSQL │ │
│ └──────────────┘ │
└──────────────────────────────────────────────────────────┘
Tech Stack
- Language: Go 1.22+ (using
http.ServeMuxwith method-based routing) - Database: PostgreSQL via
pgxdriver + sqlc code generation - Auth: JWT (HS256) + bcrypt for admin passwords
- QR Code:
skip2/go-qrcodefor PNG generation - Messaging: Meta/Facebook Graph API (WhatsApp), Resend (Email)
- Logging: Uber Zap (structured logging)
Database Schema
A single participants table with sqlc-generated queries:
CREATE TABLE participants (
id SERIAL PRIMARY KEY,
external_id UUID DEFAULT gen_random_uuid() UNIQUE,
name TEXT NOT NULL,
email TEXT,
wa_number TEXT,
sent BOOLEAN DEFAULT false,
accessed BOOLEAN DEFAULT false,
attended BOOLEAN DEFAULT false,
created_at TIMESTAMP DEFAULT NOW()
);
The external_id is a UUID used in invite links and QR codes — it’s the public-facing identifier, while id is the internal primary key.
API Design
Public Endpoints
GET /api/invite/{token}— Guest invite page dataGET /api/qr?participant_id={uuid}— QR code PNG generationGET /api/user?id={uuid}— Participant lookup
Auth Endpoints
POST /api/admin/login— JWT token issuance
Admin Endpoints (JWT required)
GET /api/admin/participants— List all participantsPOST /api/admin/participants— Add a participantPOST /api/admin/attendance— Mark attendance via QR scan
Invite Endpoints
POST /api/bulk-invite— Send invites to all unsent participants (batched at 50 with 500ms throttle)GET /api/send-invite— Send invite to a specific participant
Key Design Decisions
- sqlc over an ORM — Type-safe Go code generated from SQL queries, no runtime reflection overhead. Query intent is visible in the
.sqlfiles. - UUID external IDs — Separates internal primary keys from public identifiers, preventing enumeration attacks on the participant list.
- Batched invite sending — Bulk invites are sent in batches of 50 with rate limiting to avoid API throttling from WhatsApp/Resend.
- Method-based routing — Uses Go 1.22’s
http.ServeMuxwithGET,POSTpatterns instead of a third-party router, keeping dependencies minimal.
Frontend
The frontend uses Vite + React + Rolldown with a TypeScript codebase, featuring:
- Guest invite page with QR code display
- Admin login and dashboard
- Admin QR scanner for door check-in
Key Learnings
- sqlc is a great middle ground — You write SQL directly, get generated Go types, and avoid ORM abstraction leaks.
- Go 1.22 routing is sufficient for most APIs — The built-in
ServeMuxwith method patterns handles RESTful routing cleanly without Gorilla Mux or Chi. - WhatsApp API delivery is fragile — The Meta Graph API has strict template requirements and phone number verification. Email via Resend was more reliable as a fallback.
- QR code scanning in-browser works well — The frontend uses a JS QR scanner library for the admin door-check interface, eliminating the need for a native app.