-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Labels
Description
Overview
Build a web-based Markdown Pastebin where users can paste markdown text, get a unique shareable URL, and view rendered markdown with syntax highlighting. No authentication required — pastes are anonymous and permanent.
Requirements
- Express.js backend serving both API and static frontend
- TypeScript with strict mode
- Create paste: POST /api/pastes accepts { content, title? } and returns { id, url, createdAt }
- View paste: GET /api/pastes/:id returns { id, title, content, createdAt, viewCount }
- Each paste gets a unique 8-character alphanumeric ID (nanoid)
- View count increments on each GET request
- Recent pastes: GET /api/pastes?limit=10 returns the 10 most recent pastes (title + id + createdAt only, no content)
- Frontend served from /public as static HTML/CSS/JS
- Home page (/) has a textarea for markdown input, a live preview panel, and a Create Paste button
- After creating a paste, redirect to /:id which shows the rendered markdown
- Paste view page (/:id) shows rendered HTML from markdown with syntax highlighting for code blocks
- Raw view: GET /api/pastes/:id/raw returns plain text content with Content-Type: text/plain
- GET /health returns { status: "ok", pasteCount: N }
- In-memory storage using a Map (no database)
- Maximum paste size: 100KB — reject larger with 413
- Global error handling middleware
File Structure
src/
├── index.ts # Entry point — Express server, static files, routes
├── routes/
│ ├── pastes.ts # CRUD API handlers for pastes
│ └── health.ts # Health check endpoint
├── middleware/
│ ├── errorHandler.ts # Global error handling middleware
│ └── sizeLimit.ts # Request body size validation (100KB max)
├── lib/
│ ├── store.ts # In-memory paste storage (Map-based)
│ └── id.ts # ID generation (nanoid, 8 chars)
├── types.ts # TypeScript interfaces (Paste, CreatePasteRequest)
public/
├── index.html # Home page — create paste with live preview
├── view.html # Paste view page — rendered markdown
├── css/
│ └── style.css # Styling — clean, minimal, responsive
├── js/
│ ├── editor.js # Live preview logic (textarea to rendered markdown)
│ ├── view.js # Fetch and render paste on view page
│ └── lib/
│ └── marked.min.js # Markdown parser (bundled, no CDN)
tests/
├── pastes.test.ts # Integration tests for paste CRUD
└── store.test.ts # Unit tests for in-memory store
package.json
tsconfig.json
README.md
Dependencies
- express@4 — HTTP server and static file serving
- nanoid@5 — Unique ID generation
- typescript@5 — Type safety
- tsx@4 — TypeScript execution
- vitest@1 — Test runner
- supertest@6 — HTTP testing
Acceptance Criteria
- npm install && npm start runs the server on PORT (default 3000)
- GET / serves the home page with a textarea and live preview
- Typing markdown in the textarea shows rendered HTML in the preview panel in real-time
- POST /api/pastes with { content: "# Hello" } returns 201 with { id, url, createdAt }
- GET /api/pastes/:id returns the paste with viewCount incremented
- GET /:id serves the view page which fetches and renders the paste markdown as HTML
- Code blocks in the rendered markdown have syntax highlighting
- GET /api/pastes/:id/raw returns plain text with correct Content-Type
- GET /api/pastes?limit=5 returns the 5 most recent pastes
- POST with body larger than 100KB returns 413
- POST with empty content returns 400
- GET /api/pastes/nonexistent returns 404
- GET /health returns { status: "ok", pasteCount: N }
- TypeScript compiles with strict: true
- npm test passes all tests
- The UI is clean and responsive (works on mobile width)
Edge Cases
- Empty content field returns 400 with "content is required"
- Content over 100KB returns 413 with "paste too large"
- Non-existent paste ID returns 404 with "paste not found"
- Missing title defaults to "Untitled"
- Special characters in markdown (HTML injection) must be sanitized in rendered output
- Very long title truncated to 200 chars
- Concurrent view count increments are acceptable to lose (in-memory, no locks needed)
Reactions are currently unavailable