Building an Interactive SQL Learning Platform with React, Next.js, and SQL.js
Zero Setup, Pure Learning: Running a Complete SQL Environment in the Browser
Table of contents
- The Vision: SQL Without the Setup Ceremony
- The Technology Stack: Modern, Typed, and Component-Driven
- The Development Journey: From Concept to Implementation
- Technical Challenges and Solutions
- The Database Architecture: An E-commerce Playground
- Lessons Learned and Future Directions
- Conclusion: The Browser as a Learning Environment
Have you ever tried teaching someone SQL only to spend the first hour fighting with database installations? Or maybe you've been that frustrated beginner, wondering why you need to configure a server just to learn a query language. This exact pain point inspired me to build SQL Playground - a side project that lets anyone learn SQL directly in their browser, no setup required.
Note: While researching after development, I discovered there was once a PHP-based tool called "SQL Buddy" (last updated in 2011), which was a database administration tool for MySQL and SQLite. My project, despite the similar educational focus on SQL, is entirely unrelated and takes a completely different approach as a modern, client-side learning platform rather than a database administration tool. Coincidentally, I deployed my project to buddysql.seancoughlin.me before learning about the old SQL Buddy tool - the URL similarity is purely accidental!
The Vision: SQL Without the Setup Ceremony
Anyone who's taught SQL knows the first obstacle isn't the language itself—it's getting everyone to successfully install and configure a database system. This "setup tax" creates a significant barrier to entry, especially for beginners. My side project, SQL Playground, eliminates this friction entirely.
By leveraging SQL.js (SQLite compiled to WebAssembly) and the modern capabilities of client-side web applications, I've created an environment where learners can:
Write SQL queries directly in their browser
See results immediately with proper feedback
Progress through structured lessons that build upon each other
Practice on a realistic sample database
Save their progress locally without requiring accounts
The Technology Stack: Modern, Typed, and Component-Driven
My implementation rests on a carefully selected technology foundation that prioritizes developer experience, maintainability, and performance:
Core Stack
Next.js 15 - For its robust React framework capabilities and excellent developer experience
React 19 - Providing the component-based UI architecture
TypeScript - Adding type safety throughout the codebase
Tailwind CSS - For utility-first styling that scales elegantly
SQL.js - The magic ingredient that brings SQLite to the browser via WebAssembly
Key Implementation Details
The architecture follows a clean separation of concerns:
Data Layer:
SQL.js provides a complete SQLite database engine in the browser
Custom hooks manage database initialization and query execution
Local storage persists user progress between sessions
UI Layer:
React components for lesson content, SQL editor, and results display
Tailwind CSS for responsive, maintainable styling
Split-view interface that keeps lessons and code editor visible simultaneously
Learning Content:
Structured curriculum from SQL basics to joins and aggregations
Interactive challenges with intelligent feedback
Pre-populated e-commerce database with realistic relationships
The Development Journey: From Concept to Implementation
My development process followed an iterative approach, starting with the core functionality of executing SQL in the browser and gradually building up the educational framework around it.
Step 1: Setting Up the SQL.js Environment
The first technical challenge was properly integrating SQL.js. This required:
// Initializing SQL.js with WebAssembly
async function initializeSql() {
const SQL = await initSqlJs({
locateFile: file => `https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.8.0/${file}`
});
const db = new SQL.Database();
return { SQL, db };
}
The SQL.js initialization was straightforward once I understood the right approach to load it as a client-side module:
Step 2: Creating the Interactive SQL Editor
The SQL editor component became the heart of the application. It needed to:
Allow users to write and edit SQL
Execute queries against the in-memory database
Display results in a readable format
Provide helpful feedback on errors
The editor implementation uses React state to track the current query and results:
const [query, setQuery] = useState(initialQuery || "");
const [queryResult, setQueryResult] = useState({
results: null,
error: null
});
const executeQuery = () => {
try {
const result = onExecuteQuery(query);
setQueryResult(result);
// ... additional processing for grading/feedback
} catch (err) {
console.error("Error executing query:", err);
}
};
Step 3: Building the Learning Framework
With the technical foundation in place, we focused on crafting an effective learning experience. This meant:
Designing a curriculum that progresses logically from basic SELECT statements to complex joins
Creating a sample database with realistic relationships between customers, products, orders, etc.
Implementing a grading system that can evaluate SQL queries beyond simple string matching:
// Grading logic excerpt
export function gradeQuery(
userQuery: string,
queryResults: SqlResult[] | null,
hasError: boolean,
options: GradeOptions
): GradeResult {
// Various checks for correctness...
if (options.mustContain && options.mustContain.length > 0) {
const normalizedUserQuery = normalizeQuery(userQuery, options.caseSensitive);
const missingTerms = options.mustContain.filter((term) => {
const normalizedTerm = options.caseSensitive ? term : term.toLowerCase();
return !normalizedUserQuery.includes(normalizedTerm);
});
if (missingTerms.length > 0) {
return {
isCorrect: false,
score: 40,
feedback: `Your query is missing some important elements.`,
hints: [`Make sure your query includes: ${missingTerms.join(", ")}`],
type: "warning",
};
}
}
// Additional validation logic...
}
- Storing progress using browser local storage:
export function useLocalStorage<T>(
key: string,
initialValue: T
): [T, (value: T | ((val: T) => T)) => void, () => void] {
const [storedValue, setStoredValue] = useState<T>(initialValue);
useEffect(() => {
try {
const item = window.localStorage.getItem(key);
setStoredValue(item ? JSON.parse(item) : initialValue);
} catch (error) {
console.error(error);
setStoredValue(initialValue);
}
}, [key, initialValue]);
// ... setter and removal functions
return [storedValue, setValue, removeValue];
}
Technical Challenges and Solutions
No project is without its hurdles. Here are some of the more interesting challenges I encountered:
Challenge 1: SQL.js WebAssembly Loading
Problem: SQL.js requires WebAssembly, which has specific loading requirements in modern browsers.
Solution: I implemented a custom useSqlJs
hook that handles script loading, WebAssembly initialization, and database setup. After exploring different approaches, I found it much simpler to load SQL.js from a CDN rather than trying to manually compile it into the application. This approach simplified dependency management and ensured the WebAssembly files were properly served:
Challenge 2: Query Evaluation
Problem: Determining if a user's SQL query is "correct" is not straightforward - there are often multiple valid approaches to the same problem.
Solution: We developed a flexible grading system that can:
Check for required SQL elements (SELECT, FROM, specific table names)
Validate result structure (column names, row counts)
Provide specific feedback based on different types of errors
Challenge 3: React Hydration
Problem: Next.js server-side rendering doesn't play well with browser-only libraries like SQL.js.
Solution: We made specific components "client-only" using the "use client" directive, ensuring that SQL.js initialization only happens in the browser environment.
"use client";
import { useState, useEffect, useRef } from "react";
// SQL.js related code...
The Database Architecture: An E-commerce Playground
To provide a realistic learning environment, I created a sample e-commerce database with four key tables:
Customers: customer_id, first_name, last_name, email, join_date
Products: product_id, name, description, price, category
Orders: order_id, customer_id, order_date, total_amount
Order_Items: item_id, order_id, product_id, quantity, price_each
This schema allows for meaningful queries that demonstrate real-world SQL concepts:
Basic SELECTs for product or customer information
Filtering with WHERE to find products in specific categories
Aggregations to calculate total sales or average prices
JOINs to connect orders with customer information
Lessons Learned and Future Directions
Building SQL Playground taught me several valuable lessons:
WebAssembly is powerful but quirky - The ability to run SQLite in the browser is remarkable, but WASM has its own set of challenges around loading and memory management.
Client-side state management requires careful thinking - Without a backend, all progress tracking had to be managed through local storage with appropriate fallbacks.
Feedback is crucial for learning - Simply executing queries isn't enough; meaningful feedback dramatically improves the learning experience.
For future enhancements, I'm considering:
More advanced SQL lessons - Covering subqueries, window functions, and common table expressions
Custom database import - Allowing users to upload their own schemas
Query visualization - Displaying query execution plans visually
Performance optimizations for larger datasets
Conclusion: The Browser as a Learning Environment
SQL Playground demonstrates how modern web technologies can assist technical education. By eliminating setup friction and providing immediate feedback, I've created an environment where learning SQL becomes about the language itself, not the surrounding infrastructure.
This project showcases the power of WebAssembly to bring traditionally server-side technologies to the client, opening new possibilities for interactive learning experiences. Whether you're a SQL novice or looking to sharpen your skills, I hope this tool makes your journey more enjoyable and effective.
The code is open-source and available on GitHub, and I welcome contributions from the community to enhance this learning resource further.
This side project was built with React, Next.js, TypeScript, Tailwind CSS, and SQL.js, demonstrating how modern web technologies can create powerful, serverless applications for technical education.