A blockchain implementation with a layered Express backend and a React frontend.
For Applicants: See INSTRUCTIONS.md for task requirements (2 tasks, 4–6 hours). See SETUP.md for a quick-start guide.
hometask-blockchain/
│
├── config/
│ └── index.js # Environment config (port, CORS, blockchain settings)
│
├── models/
│ ├── blockchain.js # Block, Transaction, Blockchain domain classes
│ └── index.js # Singleton instance + demo data seeding
│
├── utils/
│ ├── logger.js # Levelled logger (error / warn / info / debug)
│ ├── response.js # Unified sendSuccess / sendCreated / sendError helpers
│ └── validator.js # isValidAddress, isValidAmount, sanitizers
│
├── middleware/
│ ├── cors.middleware.js # CORS policy
│ ├── logger.middleware.js # Morgan HTTP request logger
│ ├── errorHandler.middleware.js# Centralised error handler (must be last)
│ ├── notFound.middleware.js # 404 handler
│ ├── validateRequest.middleware.js # validateBody / validateParams factories
│ └── rateLimit.middleware.js # apiLimiter (100 req/min) + writeLimiter (20 req/min)
│
├── routes/
│ ├── index.js # Aggregates all /api sub-routes
│ ├── blockchain.routes.js # /api/chain
│ ├── transaction.routes.js # /api/transactions
│ ├── mining.routes.js # /api/mine
│ ├── balance.routes.js # /api/balance
│ ├── stats.routes.js # /api/stats
│ └── health.routes.js # /health (no rate limit)
│
├── controllers/
│ ├── blockchain.controller.js
│ ├── transaction.controller.js
│ ├── mining.controller.js
│ ├── balance.controller.js
│ └── stats.controller.js
│
├── src/ # React frontend
│ ├── api/
│ │ ├── client.js # Axios instance with request/response interceptors
│ │ ├── endpoints.js # All API URL constants
│ │ └── blockchain.api.js # Typed fetch functions (fetchChain, addTransaction…)
│ ├── hooks/
│ │ ├── useBlockchain.js # Polls /api/chain + /api/stats, returns state
│ │ └── usePolling.js # Reusable interval-based polling hook
│ ├── utils/
│ │ ├── formatters.js # truncateHash, formatTimestamp, formatAmount
│ │ └── helpers.js # isPositiveNumber, groupTransactionsByBlock, etc.
│ ├── constants/
│ │ └── index.js # POLL_INTERVAL_MS, DEFAULT_MINER_ADDRESS, enums
│ ├── components/
│ │ ├── BlockchainViewer.js
│ │ ├── TransactionForm.js
│ │ ├── StatsPanel.js
│ │ ├── Header.js
│ │ └── ErrorBoundary.js # React class error boundary
│ ├── App.js
│ └── index.js
│
├── blockchain.js # Backward-compat re-export → models/blockchain.js
├── server.js # Entry point — wires middleware, routes, starts server
├── .env.example # Template for environment variables
└── package.json
- Node.js v16 or higher
- npm
npm install
cp .env.example .env # then edit .env if you need different ports# Terminal 1 — React dev server on http://localhost:3000
npm start
# Terminal 2 — API server on http://localhost:3002, with auto-reload
npm run devThe React app proxies all /api/* requests to the API server automatically via src/setupProxy.js.
npm run serve # builds the React app, then serves everything from port 3002Copy .env.example to .env and adjust as needed.
| Variable | Default | Description |
|---|---|---|
NODE_ENV |
development |
development or production |
PORT |
3002 |
API server port |
CORS_ORIGIN |
http://localhost:3000 |
Allowed CORS origin |
BLOCKCHAIN_DIFFICULTY |
2 |
Proof-of-work difficulty |
BLOCKCHAIN_MINING_REWARD |
100 |
Coinbase reward per mined block |
INITIAL_MINER_ADDRESS |
genesis-miner |
Address for the first demo block reward |
SEED_DEMO_DATA |
true |
Set to false to start with an empty chain |
REACT_APP_API_URL |
http://localhost:3002 |
Used by the React app |
All API responses share a common envelope:
{ "success": true, ...payload }
{ "success": false, "error": "message" }| Method | Path | Description |
|---|---|---|
| GET | /api/chain |
Full chain + length |
| GET | /api/chain/valid |
{ isValid: bool } |
| Method | Path | Description |
|---|---|---|
| POST | /api/transactions |
Add a pending transaction |
| GET | /api/transactions/pending |
All pending transactions |
| GET | /api/transactions/all |
All confirmed transactions |
POST /api/transactions body:
{ "fromAddress": "address1", "toAddress": "address2", "amount": 100 }| Method | Path | Description |
|---|---|---|
| POST | /api/mine |
Mine pending transactions into a new block |
POST /api/mine body:
{ "miningRewardAddress": "miner1" }| Method | Path | Description |
|---|---|---|
| GET | /api/balance/:address |
Confirmed balance of an address |
| Method | Path | Description |
|---|---|---|
| GET | /api/stats |
Chain length, difficulty, validity, pending count |
| Method | Path | Description |
|---|---|---|
| GET | /health |
Server uptime, env, timestamp — no rate limit |
The React app is organised into distinct concerns:
src/api/— all network calls live here. Components never callfetch/axiosdirectly.src/hooks/useBlockchain— single source of truth for chain + stats state; polls every 5 s.src/utils/formatters— pure formatting functions (hash truncation, timestamps, amounts).src/constants/— magic strings and numbers in one place.ErrorBoundary— catches any unhandled React render errors gracefully.
- Node.js + Express
morgan— HTTP request loggingdotenv— environment variable loadingexpress-rate-limit— API rate limitingcors— CORS policy middleware- Node.js built-in
crypto— SHA-256 hashing
- React 18
- Axios (with interceptors)
- CSS3 (glassmorphism, gradients, animations)
Port already in use
# Use a different port
PORT=3003 npm run devFrontend can't reach the API
- Confirm
npm run devis running on port 3002 - Check
REACT_APP_API_URLin your.env - Confirm
src/setupProxy.jstarget matchesPORT
Chain resets on every restart
- This is expected until you implement Task 2 (Data Persistence) from INSTRUCTIONS.md
MIT — for learning and assessment purposes.