Development Guide¶
Set up your local development environment and learn the development workflow.
Prerequisites¶
| Requirement | Version | Purpose |
|---|---|---|
| Node.js | 18+ | Runtime environment |
| pnpm | 9+ | Package manager |
| Git | 2.30+ | Version control |
| VS Code | Latest | Recommended IDE |
Quick Start¶
# Clone the repository
git clone https://github.com/manicinc/crypto-data-aggregator.git
cd crypto-data-aggregator
# Install dependencies
pnpm install
# Set up environment
cp .env.example .env.local
# Start development server
pnpm dev
The app will be available at http://localhost:3000.
Project Structure¶
crypto-data-aggregator/
├── src/
│ ├── app/ # Next.js App Router pages
│ │ ├── api/ # API routes
│ │ ├── (routes)/ # Page routes
│ │ └── layout.tsx # Root layout
│ ├── components/ # React components
│ │ ├── ui/ # Base UI components
│ │ └── features/ # Feature components
│ ├── hooks/ # Custom React hooks
│ ├── lib/ # Utilities and services
│ └── i18n/ # Internationalization
├── public/ # Static assets
├── docs/ # Documentation
├── e2e/ # End-to-end tests
├── scripts/ # Utility scripts
└── sdk/ # Client SDKs
Environment Variables¶
Create .env.local with:
# API Configuration
NEXT_PUBLIC_API_URL=http://localhost:3000
API_SECRET_KEY=your_secret_key
# External APIs (optional for development)
COINGECKO_API_KEY=your_key
ETHERSCAN_API_KEY=your_key
# Analytics (optional)
NEXT_PUBLIC_ANALYTICS_ID=
# x402 Payments (optional)
X402_FACILITATOR_URL=https://x402.org
X402_WALLET_ADDRESS=0x...
# Database (if using persistence)
DATABASE_URL=postgresql://...
REDIS_URL=redis://localhost:6379
Development Commands¶
| Command | Description |
|---|---|
pnpm dev | Start development server |
pnpm build | Production build |
pnpm start | Start production server |
pnpm lint | Run ESLint |
pnpm lint:fix | Fix lint errors |
pnpm test | Run unit tests |
pnpm test:e2e | Run E2E tests |
pnpm type-check | TypeScript check |
Code Style¶
ESLint Configuration¶
The project uses ESLint with Next.js and TypeScript rules:
// eslint.config.mjs
import { FlatCompat } from '@eslint/eslintrc';
const compat = new FlatCompat();
export default [
...compat.extends('next/core-web-vitals', 'next/typescript'),
{
rules: {
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/no-explicit-any': 'warn',
'prefer-const': 'error',
},
},
];
Formatting¶
Prettier Integration
Install the Prettier VS Code extension for auto-formatting on save.
Component Development¶
Creating Components¶
// src/components/features/MyComponent.tsx
'use client';
import { useState } from 'react';
import { cn } from '@/lib/utils';
interface MyComponentProps {
title: string;
className?: string;
onAction?: () => void;
}
export function MyComponent({ title, className, onAction }: MyComponentProps) {
const [active, setActive] = useState(false);
return (
<div className={cn('rounded-lg p-4', className)}>
<h2>{title}</h2>
<button onClick={() => {
setActive(!active);
onAction?.();
}}>
Toggle
</button>
</div>
);
}
Storybook¶
Develop components in isolation:
Create stories for your components:
// stories/MyComponent.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { MyComponent } from '@/components/features/MyComponent';
const meta: Meta<typeof MyComponent> = {
title: 'Features/MyComponent',
component: MyComponent,
parameters: {
layout: 'centered',
},
};
export default meta;
type Story = StoryObj<typeof MyComponent>;
export const Default: Story = {
args: {
title: 'Hello World',
},
};
export const WithAction: Story = {
args: {
title: 'Click Me',
onAction: () => alert('Clicked!'),
},
};
API Development¶
Creating Endpoints¶
// src/app/api/my-endpoint/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { withAuth, withRateLimit, withX402 } from '@/lib/middleware';
export async function GET(request: NextRequest) {
// Parse query params
const searchParams = request.nextUrl.searchParams;
const limit = parseInt(searchParams.get('limit') || '10');
try {
// Fetch data
const data = await fetchData(limit);
return NextResponse.json({
success: true,
data,
timestamp: new Date().toISOString(),
});
} catch (error) {
return NextResponse.json(
{ error: 'Failed to fetch data' },
{ status: 500 }
);
}
}
// POST with authentication
export const POST = withAuth(async (request: NextRequest) => {
const body = await request.json();
// Process request
const result = await processData(body);
return NextResponse.json({ success: true, result });
});
Middleware Composition¶
// Apply multiple middleware
export const GET = withRateLimit(
withAuth(
async (request) => {
// Handler logic
}
)
);
// Or use x402 for paid endpoints
export const GET = withX402({
price: '0.001',
network: 'base',
})(async (request) => {
// Premium handler
});
Data Fetching¶
Server Components¶
// src/app/coins/page.tsx
import { getCoins } from '@/lib/api/coins';
export default async function CoinsPage() {
const coins = await getCoins();
return (
<div>
{coins.map(coin => (
<CoinCard key={coin.id} coin={coin} />
))}
</div>
);
}
Client Components with SWR¶
'use client';
import useSWR from 'swr';
import { fetcher } from '@/lib/fetcher';
export function CoinList() {
const { data, error, isLoading } = useSWR('/api/v1/coins', fetcher, {
refreshInterval: 60000, // Refresh every minute
});
if (isLoading) return <Skeleton />;
if (error) return <ErrorMessage error={error} />;
return (
<ul>
{data.coins.map(coin => (
<li key={coin.id}>{coin.name}: ${coin.price}</li>
))}
</ul>
);
}
Testing During Development¶
Running Tests¶
# Run all tests
pnpm test
# Run in watch mode
pnpm test:watch
# Run specific test file
pnpm test src/lib/utils.test.ts
# Run with coverage
pnpm test:coverage
Writing Tests¶
// src/lib/utils.test.ts
import { describe, it, expect } from 'vitest';
import { formatCurrency, formatPercentage } from './format';
describe('formatCurrency', () => {
it('formats numbers with dollar sign', () => {
expect(formatCurrency(1234.56)).toBe('$1,234.56');
});
it('handles large numbers', () => {
expect(formatCurrency(1000000)).toBe('$1,000,000.00');
});
});
Git Workflow¶
Branch Naming¶
| Type | Pattern | Example |
|---|---|---|
| Feature | feature/description | feature/whale-alerts |
| Bugfix | fix/description | fix/price-display |
| Hotfix | hotfix/description | hotfix/api-crash |
| Docs | docs/description | docs/api-guide |
Commit Messages¶
Follow conventional commits:
Examples:
feat(api): add whale tracking endpoint
fix(ui): correct price formatting for small numbers
docs(readme): update installation instructions
chore(deps): upgrade next.js to 15.1
Pull Request Process¶
- Create feature branch from
main - Make changes and commit
- Push and create PR
- Wait for CI checks
- Request review
- Squash and merge
Debugging¶
VS Code Launch Configuration¶
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Next.js: debug server-side",
"type": "node-terminal",
"request": "launch",
"command": "pnpm dev"
},
{
"name": "Next.js: debug client-side",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3000"
}
]
}
Debug Logging¶
import { logger } from '@/lib/logger';
logger.debug('Processing request', { userId, endpoint });
logger.info('Request completed', { duration: '123ms' });
logger.warn('Rate limit approaching', { remaining: 10 });
logger.error('API call failed', { error, stack: error.stack });
Hot Reload¶
The development server supports Fast Refresh:
- React Components: Preserved state on edit
- CSS/Tailwind: Instant updates
- API Routes: Automatic reload
- Server Components: Full page refresh
Next Steps¶
- Testing Guide - Write tests for your code
- Deployment Guide - Deploy to production
- Performance Guide - Optimize your code