A blockchain implementation demonstrating DDD, SOLID, KISS, DRY, and Clean Code principles.
nvm use
pnpm install
pnpm startsrc/
├── domain/
│ ├── aggregates/
│ │ └── blockchain.aggregate.js
│ ├── entities/
│ │ └── block.entity.js
│ ├── errors/
│ │ ├── domain.error.js
│ │ ├── validation.error.js
│ │ └── blockchain.error.js
│ ├── services/
│ │ └── hash.service.js
│ └── value-objects/
│ ├── hash.value-object.js
│ └── timestamp.value-object.js
│
└── infrastructure/
└── services/
└── sha256-hash.service.js
| Pattern | Example | Description |
|---|---|---|
name.entity.js |
block.entity.js |
DDD Entity (has identity) |
name.value-object.js |
hash.value-object.js |
DDD Value Object (immutable) |
name.aggregate.js |
blockchain.aggregate.js |
DDD Aggregate Root |
name.service.js |
hash.service.js |
Service (domain or infrastructure) |
name.error.js |
validation.error.js |
Custom Error Class |
| Principle | Application |
|---|---|
| S - Single Responsibility | Each class has one job |
| O - Open/Closed | HashService is extensible without modification |
| L - Liskov Substitution | Any HashService implementation can replace another |
| I - Interface Segregation | Small interfaces (hash(), verify()) |
| D - Dependency Inversion | Domain depends on abstractions |
| Pattern | Implementation |
|---|---|
| Entity | Block - unique identity (hash) |
| Value Object | Hash, Timestamp - immutable, compared by value |
| Aggregate Root | Blockchain - manages consistency |
| Domain Service | HashService - stateless operation |
| Domain Error | Exception hierarchy |
- Meaningful names
- Small, focused methods
- Direct imports (no barrel exports)
- Proper exception hierarchy
import { Blockchain } from './src/domain/aggregates/blockchain.aggregate.js';
import { Sha256HashService } from './src/infrastructure/services/sha256-hash.service.js';
const blockchain = new Blockchain(new Sha256HashService());
blockchain.addBlock({ from: 'Alice', to: 'Bob', amount: 100 });
blockchain.addBlock('Hello, Blockchain!');
console.log(blockchain.isValid());
blockchain.dump();import { HashService } from './src/domain/services/hash.service.js';
import { Blockchain } from './src/domain/aggregates/blockchain.aggregate.js';
class CustomHashService extends HashService {
hash(data) {
return '0'.repeat(64);
}
}
const blockchain = new Blockchain(new CustomHashService());// Aggregate
import { Blockchain } from './src/domain/aggregates/blockchain.aggregate.js';
// Entity
import { Block } from './src/domain/entities/block.entity.js';
// Value Objects
import { Hash } from './src/domain/value-objects/hash.value-object.js';
import { Timestamp } from './src/domain/value-objects/timestamp.value-object.js';
// Services
import { HashService } from './src/domain/services/hash.service.js';
import { Sha256HashService } from './src/infrastructure/services/sha256-hash.service.js';
// Errors
import { DomainError } from './src/domain/errors/domain.error.js';
import { ValidationError } from './src/domain/errors/validation.error.js';
import { BlockchainError } from './src/domain/errors/blockchain.error.js';// Properties
blockchain.length // Number of blocks
blockchain.genesisBlock // First block
blockchain.latestBlock // Last block
// Methods
blockchain.addBlock(data) // Add new block, returns Block
blockchain.getBlock(index) // Get block by index (-1 = last)
blockchain.findBlock(predicate) // Find first matching block
blockchain.findBlocks(predicate) // Find all matching blocks
blockchain.validate() // Returns { valid: boolean, errors: string[] }
blockchain.isValid() // Returns boolean
blockchain.toJSON(indent?) // Serialize to JSON string
blockchain.toArray() // Convert to plain objects array
blockchain.dump(options?) // Print to console
blockchain.entries() // Returns iterator with [index, block]
// Iteration
for (const block of blockchain) { }// Properties (read-only)
block.index // Position in chain
block.previousHash // Hash value object
block.data // Stored data
block.timestamp // Timestamp value object
block.hash // Hash value object
block.isGenesis // Boolean
// Methods
block.isHashValid() // Verify hash integrity
block.calculateHash() // Recalculate hash
block.toObject() // Convert to plain object
// Static
Block.fromObject(obj, hashService) // Reconstruct from plain object// Static
Hash.LENGTH // 64
Hash.GENESIS // Genesis block's previous hash (64 zeros)
Hash.PATTERN // Validation regex
// Instance
hash.value // String value
hash.equals(other) // Compare with another Hash
hash.toString() // String representation
hash.toJSON() // For JSON serialization// Static
Timestamp.now() // Create current timestamp
// Instance
timestamp.value // ISO 8601 string
timestamp.date // Date object (copy)
timestamp.equals(other)
timestamp.isBefore(other)
timestamp.isAfter(other)
timestamp.toString()
timestamp.toJSON()// Abstract methods
hashService.hash(data) // Returns hash string
hashService.verify(data, expectedHash) // Returns boolean// DomainError (base)
error.message // Error message
error.code // Error code
error.name // Class name
// ValidationError extends DomainError
error.field // Field that failed validation
// BlockchainError extends DomainError
error.blockIndex // Problematic block indexThe app.js demo generates fake data using @faker-js/faker:
- User registrations
- Cryptocurrency transactions (BTC, ETH, SOL, etc.)
- Document verifications
- NFT mints
- Smart contract deployments
Each run produces different data.
- Node.js 20+
MIT
Gustavo Franco - gustavocfranco@gmail.com - @gufranco