18 KiB
Development Guide
Welcome to the Feuerwehr Dashboard development guide. This document will help you get started with developing, testing, and contributing to the project.
Table of Contents
- Developer Onboarding
- Local Development Setup
- Project Structure
- Code Architecture
- Development Workflow
- Adding New Features
- Testing Guidelines
- Debugging
- Common Issues
- Best Practices
Developer Onboarding
Prerequisites
Before you begin, ensure you have:
- Node.js 18+ - Download
- Docker Desktop - Download
- Git - Download
- Code Editor - VS Code recommended
- Authentik Access - For testing authentication
Recommended VS Code Extensions
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"ms-vscode.vscode-typescript-next",
"bradlc.vscode-tailwindcss",
"ms-azuretools.vscode-docker",
"prisma.prisma"
]
}
First Steps
-
Clone the repository:
git clone <repository-url> cd feuerwehr_dashboard -
Read the documentation:
- README.md - Project overview
- ARCHITECTURE.md - System design
- API_DOCUMENTATION.md - API reference
-
Set up your environment (see below)
-
Run the application locally
-
Make a small change to familiarize yourself with the codebase
Local Development Setup
Option 1: Docker-Based Development (Recommended for Beginners)
Start the development database:
make dev
This starts only PostgreSQL, allowing you to run backend/frontend locally with hot reload.
Option 2: Fully Dockerized (Quick Testing)
Run everything in Docker:
make prod
Note: This doesn't support hot reload, so it's less ideal for active development.
Option 3: Native Development (Full Control)
Step 1: Start Database
make dev
Or manually:
docker-compose -f docker-compose.dev.yml up -d
Step 2: Configure Backend
Create backend/.env:
cd backend
cat > .env << EOF
DATABASE_URL=postgresql://dev_user:dev_password@localhost:5432/feuerwehr_dev
JWT_SECRET=dev_secret_do_not_use_in_production
NODE_ENV=development
PORT=3000
LOG_LEVEL=debug
# Authentik - use your dev instance
AUTHENTIK_CLIENT_ID=your_dev_client_id
AUTHENTIK_CLIENT_SECRET=your_dev_client_secret
AUTHENTIK_ISSUER=http://localhost:9000/application/o/feuerwehr/
AUTHENTIK_REDIRECT_URI=http://localhost:5173/auth/callback
EOF
Step 3: Install Backend Dependencies
cd backend
npm install
Step 4: Run Backend
npm run dev
Backend will start on http://localhost:3000 with auto-reload on file changes.
Step 5: Configure Frontend
Create frontend/.env:
cd frontend
cat > .env << EOF
VITE_API_URL=http://localhost:3000
EOF
Step 6: Install Frontend Dependencies
cd frontend
npm install
Step 7: Run Frontend
npm run dev
Frontend will start on http://localhost:5173 with hot module replacement.
Verify Setup
Test backend:
curl http://localhost:3000/health
# Should return: {"status":"ok","timestamp":"..."}
Test frontend:
open http://localhost:5173
# Browser should open with login page
Project Structure
Backend Structure
backend/
├── src/
│ ├── config/ # Configuration files
│ │ ├── database.ts # Database connection and pool
│ │ ├── environment.ts # Environment variables
│ │ └── oauth.ts # OAuth/Authentik configuration
│ ├── controllers/ # Request handlers
│ │ ├── auth.controller.ts
│ │ └── user.controller.ts
│ ├── database/
│ │ └── migrations/ # SQL migration files
│ │ ├── 001_create_users_table.sql
│ │ └── 002_create_sessions_table.sql
│ ├── middleware/ # Express middleware
│ │ ├── auth.middleware.ts # Authentication
│ │ ├── error.middleware.ts # Error handling
│ │ └── rate-limit.middleware.ts # Rate limiting
│ ├── models/ # Data models
│ │ └── user.model.ts
│ ├── routes/ # API routes
│ │ ├── auth.routes.ts
│ │ └── user.routes.ts
│ ├── services/ # Business logic
│ │ ├── auth.service.ts
│ │ ├── oauth.service.ts
│ │ ├── token.service.ts
│ │ └── user.service.ts
│ ├── types/ # TypeScript types
│ │ ├── auth.types.ts
│ │ └── user.types.ts
│ ├── utils/ # Utility functions
│ │ └── logger.ts
│ ├── app.ts # Express app configuration
│ └── server.ts # Server entry point
├── logs/ # Application logs (gitignored)
├── Dockerfile
├── package.json
├── tsconfig.json
└── nodemon.json
Frontend Structure
frontend/
├── src/
│ ├── components/ # Reusable React components
│ │ ├── common/ # Shared components
│ │ │ ├── Header.tsx
│ │ │ └── Loading.tsx
│ │ └── layout/ # Layout components
│ │ └── MainLayout.tsx
│ ├── contexts/ # React Context providers
│ │ └── AuthContext.tsx # Authentication state
│ ├── pages/ # Page components
│ │ ├── auth/
│ │ │ ├── LoginPage.tsx
│ │ │ └── CallbackPage.tsx
│ │ ├── dashboard/
│ │ │ └── DashboardPage.tsx
│ │ └── user/
│ │ └── ProfilePage.tsx
│ ├── services/ # API service layer
│ │ ├── api.ts # Axios instance
│ │ ├── authService.ts # Auth API calls
│ │ └── userService.ts # User API calls
│ ├── theme/ # MUI theme configuration
│ │ └── theme.ts
│ ├── types/ # TypeScript types
│ │ └── auth.types.ts
│ ├── utils/ # Utility functions
│ │ └── storage.ts # Local storage helpers
│ ├── App.tsx # Main app component
│ ├── main.tsx # Entry point
│ └── vite-env.d.ts # Vite type declarations
├── public/ # Static assets
├── nginx.conf # Production Nginx config
├── Dockerfile
├── package.json
├── vite.config.ts
└── tsconfig.json
Code Architecture
Backend Architecture
Layered Architecture
-
Routes Layer (
routes/)- Define API endpoints
- Input validation (Zod schemas)
- Route to controllers
-
Controllers Layer (
controllers/)- Handle HTTP requests/responses
- Call services for business logic
- Format responses
-
Services Layer (
services/)- Business logic implementation
- Database operations
- External API calls (OAuth)
-
Models Layer (
models/)- Data structures
- Database queries
- Data validation
-
Middleware Layer (
middleware/)- Authentication
- Error handling
- Rate limiting
- Logging
Request Flow
Client Request
↓
Express Router (routes/)
↓
Middleware (auth, validation)
↓
Controller (controllers/)
↓
Service (services/)
↓
Model (models/)
↓
Database
↓
← Response flows back up
Frontend Architecture
Component-Based Architecture
- Pages - Route-level components
- Layouts - Structural components (header, sidebar)
- Components - Reusable UI elements
- Contexts - Global state management
- Services - API communication
Data Flow
User Interaction
↓
Component Event Handler
↓
Service Layer (API call)
↓
Backend API
↓
Update Context/State
↓
Re-render Components
Authentication Flow
1. User clicks "Login"
↓
2. Frontend redirects to Authentik
↓
3. User authenticates with Authentik
↓
4. Authentik redirects to /auth/callback with code
↓
5. Frontend sends code to backend /api/auth/callback
↓
6. Backend exchanges code for tokens with Authentik
↓
7. Backend creates user session in database
↓
8. Backend returns JWT access/refresh tokens
↓
9. Frontend stores tokens and updates AuthContext
↓
10. User is redirected to dashboard
Development Workflow
1. Create Feature Branch
git checkout -b feature/your-feature-name
# or
git checkout -b fix/bug-description
2. Make Changes
- Write code following style guidelines
- Add comments for complex logic
- Update types as needed
3. Test Locally
# Run backend tests
cd backend && npm test
# Run frontend tests
cd frontend && npm test
# Manual testing
# Test in browser at http://localhost:5173
4. Commit Changes
git add .
git commit -m "feat: add user profile editing feature"
Follow Conventional Commits:
feat:- New featurefix:- Bug fixdocs:- Documentationrefactor:- Code refactoringtest:- Adding testschore:- Maintenance
5. Push and Create PR
git push origin feature/your-feature-name
Create pull request on your Git platform.
Adding New Features
Adding a New API Endpoint
Step 1: Define Types
Create or update backend/src/types/your-feature.types.ts:
export interface YourFeature {
id: number;
name: string;
createdAt: Date;
}
export interface CreateYourFeatureDto {
name: string;
}
Step 2: Create Model
Create backend/src/models/your-feature.model.ts:
import { pool } from '../config/database';
import { YourFeature } from '../types/your-feature.types';
export const createYourFeature = async (name: string): Promise<YourFeature> => {
const result = await pool.query(
'INSERT INTO your_features (name) VALUES ($1) RETURNING *',
[name]
);
return result.rows[0];
};
Step 3: Create Service
Create backend/src/services/your-feature.service.ts:
import * as YourFeatureModel from '../models/your-feature.model';
import { CreateYourFeatureDto } from '../types/your-feature.types';
class YourFeatureService {
async create(dto: CreateYourFeatureDto) {
// Business logic here
return await YourFeatureModel.createYourFeature(dto.name);
}
}
export default new YourFeatureService();
Step 4: Create Controller
Create backend/src/controllers/your-feature.controller.ts:
import { Request, Response } from 'express';
import yourFeatureService from '../services/your-feature.service';
class YourFeatureController {
async create(req: Request, res: Response) {
try {
const feature = await yourFeatureService.create(req.body);
res.status(201).json(feature);
} catch (error) {
res.status(500).json({ error: 'Failed to create feature' });
}
}
}
export default new YourFeatureController();
Step 5: Create Routes
Create backend/src/routes/your-feature.routes.ts:
import { Router } from 'express';
import yourFeatureController from '../controllers/your-feature.controller';
import { authenticate } from '../middleware/auth.middleware';
const router = Router();
router.post('/', authenticate, yourFeatureController.create);
export default router;
Step 6: Register Routes
Update backend/src/app.ts:
import yourFeatureRoutes from './routes/your-feature.routes';
app.use('/api/your-features', yourFeatureRoutes);
Step 7: Create Database Migration
Create backend/src/database/migrations/00X_create_your_features_table.sql:
CREATE TABLE IF NOT EXISTS your_features (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_your_features_name ON your_features(name);
Adding a New Frontend Page
Step 1: Create Page Component
Create frontend/src/pages/your-feature/YourFeaturePage.tsx:
import React from 'react';
import { Container, Typography } from '@mui/material';
const YourFeaturePage: React.FC = () => {
return (
<Container>
<Typography variant="h4">Your Feature</Typography>
</Container>
);
};
export default YourFeaturePage;
Step 2: Create Service
Create frontend/src/services/yourFeatureService.ts:
import api from './api';
export const createYourFeature = async (name: string) => {
const response = await api.post('/api/your-features', { name });
return response.data;
};
Step 3: Add Route
Update frontend/src/App.tsx:
import YourFeaturePage from './pages/your-feature/YourFeaturePage';
// In your routes
<Route path="/your-feature" element={<YourFeaturePage />} />
Testing Guidelines
Backend Testing
Create backend/src/__tests__/your-feature.test.ts:
import yourFeatureService from '../services/your-feature.service';
describe('YourFeatureService', () => {
test('should create feature', async () => {
const feature = await yourFeatureService.create({ name: 'Test' });
expect(feature.name).toBe('Test');
});
});
Run tests:
cd backend
npm test
Frontend Testing
Create frontend/src/__tests__/YourFeaturePage.test.tsx:
import { render, screen } from '@testing-library/react';
import YourFeaturePage from '../pages/your-feature/YourFeaturePage';
test('renders your feature page', () => {
render(<YourFeaturePage />);
expect(screen.getByText('Your Feature')).toBeInTheDocument();
});
Run tests:
cd frontend
npm test
Manual Testing Checklist
- Feature works in development mode
- Feature works in production build
- Authenticated users can access
- Unauthorized access is blocked
- Error states are handled
- Loading states are shown
- Responsive on mobile
- Works in Chrome, Firefox, Safari
Debugging
Backend Debugging
VS Code Launch Configuration
Create .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug Backend",
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "dev"],
"cwd": "${workspaceFolder}/backend",
"console": "integratedTerminal"
}
]
}
Console Logging
import logger from '../utils/logger';
logger.debug('Debug message', { data });
logger.info('Info message');
logger.warn('Warning message');
logger.error('Error message', { error });
Database Queries
Enable query logging in backend/src/config/database.ts:
const pool = new Pool({
// ...config
log: (msg) => logger.debug(msg)
});
Frontend Debugging
React DevTools
Install React Developer Tools browser extension.
Console Logging
console.log('Data:', data);
console.error('Error:', error);
Network Tab
Use browser DevTools Network tab to inspect API calls.
State Debugging
const [state, setState] = useState(initialState);
useEffect(() => {
console.log('State changed:', state);
}, [state]);
Common Issues
Database Connection Failed
Problem: Backend can't connect to database
Solution:
# Verify database is running
docker ps | grep postgres
# Restart database
docker-compose -f docker-compose.dev.yml restart
# Check logs
docker logs feuerwehr_db_dev
Port Already in Use
Problem: Port 3000 or 5173 already occupied
Solution:
# Find process using port
lsof -i :3000
# Kill process
kill -9 <PID>
# Or change port in package.json
TypeScript Errors
Problem: Type errors in VS Code
Solution:
# Restart TypeScript server in VS Code
# Cmd+Shift+P > "TypeScript: Restart TS Server"
# Rebuild
cd backend && npm run build
cd frontend && npm run build
Authentik Redirect Fails
Problem: OAuth callback returns error
Solution:
- Verify redirect URI exactly matches in Authentik
- Check client ID and secret in
.env - Ensure Authentik is accessible from browser
- Check browser console for CORS errors
Hot Reload Not Working
Problem: Changes don't reflect immediately
Solution:
# Backend - check nodemon is running
# Frontend - restart Vite dev server
npm run dev
Best Practices
Code Style
- Use TypeScript strict mode
- Follow ESLint rules
- Use Prettier for formatting
- Write descriptive variable names
- Add JSDoc comments for public APIs
Security
- Never commit secrets to Git
- Validate all inputs
- Use parameterized queries
- Sanitize user input
- Keep dependencies updated
Performance
- Use database indexes
- Implement pagination for lists
- Cache expensive operations
- Optimize bundle size
- Use React.memo for expensive components
Git Workflow
- Keep commits small and focused
- Write clear commit messages
- Rebase before merging
- Delete branches after merge
- Don't commit
node_modules/or.env
Getting Help
- Documentation: Check all
.mdfiles - Code Comments: Read inline documentation
- Team Chat: Ask in development channel
- GitHub Issues: Search existing issues
- Stack Overflow: Search for similar problems
Next Steps
After completing setup:
- Explore the codebase
- Try adding a simple feature
- Read ARCHITECTURE.md
- Review API_DOCUMENTATION.md
- Check CONTRIBUTING.md
Happy coding!