CLI tool
hristo2612/SQLNoir avatar
hristo2612/SQLNoir

SQLNoir: a detective game where the query is the puzzle

Solve mysteries through SQL.

1,667 stars73 forksTypeScriptMIT

At a glance

What is it?
SQLNoir is a Next.js application that turns SQL practice into a detective game, with case files that run from simple thefts to complex murders, a SQLite engine in the browser through sql.js, and Supabase for authentication and data. The setup is the interesting part, since the game needs a Supabase project, a linked CLI and pushed migrations before the first case loads.
Who is it for?
Adopt SQLNoir if you want a way to teach SQL that people will actually finish, or if you want ready made case files for a classroom, and if you are willing to create a Supabase project and run the CLI steps before the game appears. Do not adopt it as a query optimisation benchmark, since the cases are puzzles with a single intended answer rather than performance scenarios.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 108 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The query is the puzzle, not the exercise

SQL Noir is an interactive mystery-solving game where you crack cases by writing SQL queries, and the pitch in the README is that you step into the shoes of a real detective. Each case file presents you with a unique crime scenario and a database full of evidence.

The list of objectives is what makes the framing work. You uncover suspicious patterns in the data, track down missing records, connect the dots between suspects, expose fraudulent transactions, and piece together the evidence using SQL.

Notice what is not in that list. There is no mention of performance, no mention of query plans, and no mention of optimising anything. The learning objective is retrieval and reasoning, the kind of work a person does when they are handed an unfamiliar database and asked a question about it.

That framing is also what makes the project useful beyond a game. A case with a set of tables and a question about them is a fixture, and a teacher can use the case files as exercises, an interview can use them as a screening task, and a developer can use them to check whether they can write a join without looking at the answer. The game loop is the presentation, the SQL is the content.

The cases are graded by difficulty, and the README describes the range as going from simple thefts to complex murders, with a rank that runs from rookie to master detective. The project is MIT licensed, the default branch is main, the site is sqlnoir.com, and the last push was on 2026-07-07. There are no GitHub releases, which is consistent with the package version of 0.0.0 and the private flag in the manifest, since this is the source of the application rather than a published package.

sql.js in the browser and Supabase behind it

The dependency list explains the architecture, and two entries do most of the work.

sql.js is the SQL engine that runs in the browser, which is what makes the game possible without a round trip per query. The player writes a query, it runs locally against a database in the page, and the result comes back immediately. The other important piece is reactflow, the node graph library, which is almost certainly the detective interface, the visual graph of suspects, victims and relationships that the game draws while you work. lucide-react and react-icons supply the icon set, and canvas-confetti supplies the celebration when a case is solved.

Around that, the application is a fairly standard Next.js 14.2.35 app on React 18 with TypeScript 5.5.3, styled with Tailwind CSS 3.4 and PostCSS. next-intl handles the localisation, which is what makes the three languages in the README possible, and the messages/ directory in the repository is where the translations live.

Then there is the platform layer. Supabase provides authentication through @supabase/supabase-js and @supabase/auth-helpers-nextjs, and it is where the case data and the player records live. PostHog is the analytics, in the browser with posthog-js and on the server with posthog-node. Stripe handles payments, with @stripe/stripe-js for the browser and the stripe package for the server, and the .env.example file mentions a product image shown on the Stripe Checkout, which points to a paid tier for the game.

Vercel analytics is also in the dependency list, so the application is built for a Vercel deployment, with Supabase doing the stateful part.

The setup needs a Supabase project before the first case

The getting started guide is eleven steps, and it is worth reading as a whole because the game is not a static download you can unzip.

The prerequisite is Node.js 18.17 and later, which the README attributes to the Next.js requirement, and the manifest agrees with an engines field of node >=18.17.0. After cloning, the dependencies go in with npm install.

Then the Supabase half. You create a new project at supabase.com, copy the generated anon public key and the Project URL from it, and put them into a .env.local file at the root of the project:

bash
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key
NEXT_PUBLIC_SUPABASE_URL=your_supabase_url

The README notes that legacy VITE_ variables are still read, but that NEXT_PUBLIC_ is recommended.

The Supabase CLI does the rest, and the order matters. You install the CLI following the instructions in its documentation, log in with supabase login, then link the project with supabase link, which has to be run from the root of the project, and you select the project you just created when prompted. The database migrations are then pushed:

bash
supabase db push

Finally the development server starts with npm run dev, and the browser opens http://localhost:3000.

The sequence means a case database has to exist before the game is playable, and the supabase/ directory in the repository is what the migrations come from. For someone who wants to read the code rather than play the game, that is a heavier setup than it looks, and it is the main practical objection to the project as a self hosted teaching tool.

The .env.example file is the real configuration documentation

The .env.example file at the repository root is better documented than the README, and it is the place to look when you deploy this.

It opens by saying to copy it to .env.local and fill in real values, that you should never commit .env.local or real secret values, and that placeholders only belong in the example file. It also draws the line that matters in a Next.js application, that NEXT_PUBLIC_ variables are exposed to the browser while everything else is server-only.

Under Supabase there are four settings. NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY are the project URL and the anon public key used by the browser client. NEXT_PUBLIC_SUPABASE_REDIRECT_URL is where Supabase redirects after an OAuth sign-in, for example a Google login, and the comment says it defaults to the current origin if unset and should be set explicitly in production. SUPABASE_SERVICE_ROLE_KEY is the one that needs care, since the comment marks it server-side only, for admin and license operations, and says to keep it secret. The legacy VITE_ prefixed variables are listed as still referenced by older scripts, with a note to mirror the NEXT_PUBLIC_ values if needed.

The rest of the file is the operational surface. PostHog has NEXT_PUBLIC_POSTHOG_KEY and NEXT_PUBLIC_POSTHOG_HOST, with the comment that without the key analytics silently no-op in development, which is a useful thing to know before you conclude the tracking is broken. Stripe has NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET and an optional STRIPE_PRODUCT_IMAGE_URL, and each is marked as browser exposed or server only. Google sign-in is not configured through the environment at all, since the comment says it is set up in the Supabase dashboard under Authentication and Providers.

For a deployment, that file is the checklist.

The detective interface and the difficulty ladder

Two things about the game design are worth separating from the tooling.

The first is the difficulty ladder, and it is a design constraint for anyone contributing cases. The README says the cases range from simple thefts to complex murders, organised by difficulty level, and that the player climbs from rookie to master detective. A graded ladder only works if the cases are authored in order, which means the case set is a curriculum rather than a pile, and it means a contributor who adds a case has to place it on that ladder.

The second is the interface. The dependency on reactflow says the evidence is presented as a graph, and the presence of confetti says there is a moment of resolution. Neither of those details is documented in the README, so an evaluator is inferring the shape of the experience from the dependency list, and the contributing guidelines are the place where the real details are.

That is worth saying plainly, because the README is short and the project asks for contributions in these areas: submitting new cases for others to solve, improving existing investigations, fixing bugs in the system, enhancing the detective interface, and improving documentation. Five areas, and the two that need the most project knowledge are the case format and the interface, neither of which is described in the README.

The repository layout backs this up. There is a docs/ directory, a Plan.md, an SEO-full-1.md and a blog-slug.md, which reads like a site with content strategy attached, and a Contributors.md alongside the CONTRIBUTING.md the README points to.

Three languages, and one classroom in Shanghai

The localisation section is short but it is the most distinctive thing about the project, and it is not a machine translation story.

SQLNoir is available in English, which is the original, in Portuguese from Brazil, which the README credits as a community contribution, and in Simplified Chinese, which is described as adapted and culturally localised by Lloyd Hasson. The README explains what that involved in some detail: Lloyd is a teacher in China who built a complete Chinese SQLNoir from scratch for his classroom, translating every case into immersive Mandarin with localised character names, Shanghai settings, and Chinese named SQL tables and columns, and then donated the work to the official project.

That detail is the important one, and it has a technical consequence that a translation tool would miss. If the tables and columns are named in Chinese, then the SQL a player writes contains Chinese identifiers, and the whole exercise depends on the engine handling them. That is a portability check on the case format itself, and it means the Chinese cases are not a locale file over the English cases, they are a separate set of cases.

It also tells you what the project is for as much as who uses it. A teacher who needed a complete set of localised cases, and who had time to translate every scenario and rename every table, was solving a classroom problem. The game is the vehicle, and the case format is the artefact.

For a contributor, the same observation applies. Adding a case means writing a scenario, a schema and a question, and the three existing languages show how much latitude there is in naming and setting, since the Shanghai cases use different names and different tables entirely.

Editorial conclusion

Adopt SQLNoir if you want a way to teach SQL that people will actually finish, or if you want ready made case files for a classroom, and if you are willing to create a Supabase project and run the CLI steps before the game appears. Do not adopt it as a query optimisation benchmark, since the cases are puzzles with a single intended answer rather than performance scenarios. Verify first that the case format documented in CONTRIBUTING.md fits the cases you want to write, that your Node 18.17 or newer environment can run the Next.js 14 build, and that the Supabase credentials in .env.local are the anon key and the project URL, not the service role key.

Frequently asked questions

How do I run SQLNoir locally?

You need Node.js 18.17 or later, then npm install, a Supabase project with its anon key and project URL in a .env.local file, the Supabase CLI logged in with supabase login, the project linked with supabase link from the repository root, the migrations pushed with supabase db push, and finally npm run dev, which serves the game at http://localhost:3000.

What database engine does SQLNoir run the player's SQL against?

The dependency list includes sql.js, a SQLite engine that runs in the browser, so queries execute locally in the page. Supabase is used for authentication, the case data and the player records, and the migrations come from the supabase/ directory in the repository.

Which environment variables does SQLNoir need?

The .env.example file lists NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, NEXT_PUBLIC_SUPABASE_REDIRECT_URL and the server-only SUPABASE_SERVICE_ROLE_KEY, plus PostHog and Stripe variables and the legacy VITE_ prefixed Supabase names. It warns that NEXT_PUBLIC_ variables are exposed to the browser and that .env.local must never be committed.

How do I contribute a new case to SQLNoir?

The README lists what is wanted, including submitting new cases for others to solve, improving existing investigations, fixing bugs, enhancing the detective interface and improving documentation, and points to the CONTRIBUTING.md file for the guidelines. The cases are graded by difficulty, from simple thefts to complex murders.

Official sources

  1. hristo2612/SQLNoir on GitHub
  2. Issues
  3. License: MIT
  4. Project website
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/hristo2612-sqlnoir.svg)](https://hysenlabs.com/projects/hristo2612-sqlnoir)

Was this useful?