Your personal JSON storage API
A full-stack web application for storing, managing, and serving JSON data via a simple REST API.
Create stores, issue scoped API keys, and track every request — all in one place.
- Overview
- Features
- Tech Stack
- Getting Started
- API Reference
- Project Structure
- Database Schema
- Screenshots
- Learning Goals
- License
JSON Server is a hands-on learning project that demonstrates the architecture of a modern, production-grade full-stack web application. It covers the complete lifecycle of a real-world API service — from user authentication and data management to access control and request logging.
The project is intentionally kept simple and readable, making it ideal for developers learning how to build full-stack applications with the modern Next.js App Router paradigm.
- JSON Stores — Create and manage named JSON data stores with a built-in code editor (syntax highlighting, validation, auto-format)
- Public / Private visibility — Toggle each store between public (no auth) and private (API key required)
- Deep merge on PUT — Incoming JSON is recursively merged into existing content; arrays are replaced
- API Key management — Issue scoped keys with
get,put, orget,putpermissions, linked to specific stores - Access Logs — Every API call is logged with timestamp, IP, user agent, and full request/response body
- Email verification — Registration requires email confirmation via Resend before first login
- Rate limiting — 100 requests per minute per API key (sliding window)
- Dark / Light mode — Full theme support with instant switching
- Responsive — Works on desktop and mobile
| Layer | Technology |
|---|---|
| Framework | Next.js 15 (App Router) |
| UI | React 19, Tailwind CSS v4, shadcn/ui, Base UI |
| Language | TypeScript 5 |
| Auth | NextAuth v5 (Credentials + email verification) |
| ORM | Drizzle ORM |
| Database | MySQL 8 |
| Resend | |
| Runtime | Bun |
| Animation | Motion (Framer Motion v12) |
| Editor | CodeMirror (JSON) |
# Clone the repository
git clone https://github.com/your-username/json-server.git
cd json-server
# Install dependencies
bun installCreate a .env.local file in the root of the project:
# Database
DATABASE_URL="mysql://root:password@localhost:3306/json-server"
# NextAuth
AUTH_SECRET="your-secret-key-min-32-chars"
AUTH_URL="http://localhost:3000"
AUTH_TRUST_HOST=true
# Email (Resend)
RESEND_API_KEY="re_xxxxxxxxxxxxxxxxxxxx"
# App
NEXT_PUBLIC_APP_URL="http://localhost:3000"| Variable | Description |
|---|---|
DATABASE_URL |
MySQL connection string |
AUTH_SECRET |
Random string ≥ 32 characters — used to sign sessions |
AUTH_URL |
Public base URL of the app |
AUTH_TRUST_HOST |
Set to true when running behind a proxy or on localhost |
RESEND_API_KEY |
API key from resend.com |
NEXT_PUBLIC_APP_URL |
Used to build verify-email links sent via email |
# Push schema to database (creates all tables)
bun run db:push
# Optional: open Drizzle Studio to browse data visually
bun run db:studio# Development
bun run dev
# Production build + start
bun run build
bun run start
# Weekly log cleanup cron (optional, runs in background)
bun run cronAll API routes are under /api/json/[id]. Every request — successful or not — is logged to the logs table.
Retrieve a JSON store by ID.
Public store — no authentication required:
GET /api/json/ef1b0011-1fad-4f67-a17b-fcff2fe42875Private store — API key with get permission required:
GET /api/json/ef1b0011-1fad-4f67-a17b-fcff2fe42875
Authorization: Bearer json-server-xxxxxxxxxxxxxxxxSuccess response 200 OK:
{
"id": "ef1b0011-1fad-4f67-a17b-fcff2fe42875",
"name": "My Store",
"content": { "key": "value" },
"updated_at": "2025-06-11T03:00:00.000Z"
}Error responses:
| Status | Reason |
|---|---|
401 Unauthorized |
Missing or invalid API key / insufficient permissions |
404 Not Found |
Store ID does not exist |
429 Too Many Requests |
Rate limit exceeded |
Deep-merge new data into an existing JSON store. Always requires an API key with put permission.
PUT /api/json/ef1b0011-1fad-4f67-a17b-fcff2fe42875
Authorization: Bearer json-server-xxxxxxxxxxxxxxxx
Content-Type: application/json
{
"user": { "name": "Alice" }
}Deep merge rules:
| Type | Behavior |
|---|---|
| Object | Keys are recursively merged |
| Array | Incoming value replaces existing |
| Primitive | Incoming value replaces existing |
Example:
Success response 200 OK: full merged content (same shape as GET response)
Error responses:
| Status | Reason |
|---|---|
400 Bad Request |
Request body is not valid JSON |
401 Unauthorized |
Missing, invalid, or insufficient-permission API key |
404 Not Found |
Store ID does not exist |
429 Too Many Requests |
Rate limit exceeded |
Authenticated requests (private GET and all PUT) are rate-limited to 100 requests per minute per API key, using a sliding window algorithm.
Every authenticated response includes these headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 94
X-RateLimit-Reset: 2025-06-11T04:01:00.000ZWhen the limit is exceeded (429):
Retry-After: 42
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 2025-06-11T04:01:00.000Z{
"error": "Rate limit exceeded. Max 100 requests per minute per API key."
}Note: Public GET stores are not rate-limited. Only API-key-authenticated requests count toward the limit.
json-server/
├── app/
│ ├── (auth)/ # Login, register, verify-email pages
│ ├── (dashboard)/ # Protected dashboard pages
│ │ └── dashboard/
│ │ ├── json/ # JSON store CRUD + Monaco editor
│ │ ├── keys/ # API key management
│ │ ├── logs/ # Paginated, filterable log viewer
│ │ └── settings/ # Profile, password, theme
│ ├── api/
│ │ └── json/[id]/ # Public REST API — GET + PUT
│ ├── layout.tsx
│ └── page.tsx # Marketing / landing page
│
├── components/
│ ├── ui/ # shadcn/ui primitives
│ ├── shared/ # Reusable app-level components
│ ├── json/ # JSON store UI components
│ ├── keys/ # API key UI components
│ └── logs/ # Log table and filter components
│
├── lib/
│ ├── actions/ # Next.js Server Actions (mutations)
│ ├── data/ # Server-side data access functions
│ ├── db/ # Drizzle client + schema definitions
│ ├── email/ # Resend templates
│ └── utils/ # merge, crypto, rate-limit
│
├── scripts/
│ ├── cron.ts # Weekly log cleanup (Bun cron)
│ └── screenshots.ts # Automated screenshot capture (Playwright)
│
└── public/
└── screenshots/ # App screenshots — light + dark WebP
users ──────────────────────── json_stores
│ │
├── verification_tokens │
├── api_keys ─────── api_key_json_stores
└── logs
| Table | Purpose |
|---|---|
users |
Registered accounts (name, email, hashed password) |
verification_tokens |
Email verification tokens — expire in 24 hours |
json_stores |
JSON data stores (content stored as longtext) |
api_keys |
API keys with get, put, or get,put permissions |
api_key_json_stores |
Many-to-many join: keys ↔ stores |
logs |
Full audit log of every API request |
| Light mode | Dark mode |
|---|---|
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
This project is structured to teach the following concepts through a real working application:
| # | Concept | What you'll learn |
|---|---|---|
| 01 | REST API Design | HTTP methods, status codes, JSON response structure, deep-merge logic |
| 02 | Authentication | Registration flow, email verification, credential login, session middleware |
| 03 | Access Control | API key generation, scoped permissions, resource-level authorization |
| 04 | Database & ORM | Schema design, relationships, migrations, type-safe queries with Drizzle |
| 05 | Server Components | App Router data fetching, Server Actions, mixing server and client rendering |
| 06 | Audit Logging | Capturing request metadata, building filterable log viewers |
| 07 | Rate Limiting | Sliding window algorithm, standard X-RateLimit-* response headers |
| 08 | Email Flows | Transactional email with Resend, token-based email verification |
MIT © 2025 Khan Trần





