Loading...
Loading...
Frontend-backend integration patterns, CORS configuration, API contract validation, and build hygiene for full-stack TypeScript applications. Use when integrating separate services or debugging cross-origin issues.
npx skill4agent add vineethsoma/agent-packages integration-testing// ❌ WRONG - Single hardcoded origin
const CORS_ORIGIN = 'http://localhost:5173';
// ✅ CORRECT - Support port range
const CORS_ORIGINS = (process.env.CORS_ORIGIN ||
'http://localhost:5173,http://localhost:5174,http://localhost:5175'
).split(',').map(s => s.trim());
app.use(cors({ origin: CORS_ORIGINS, credentials: true }));# backend/.env
CORS_ORIGIN=http://localhost:5173,http://localhost:5174,http://localhost:5175
# backend/.env.example (commit this)
CORS_ORIGIN=http://localhost:5173,http://localhost:5174,http://localhost:5175// Shared type (ideal design, not actual API)
interface SearchResult {
bird: Bird; // ❌ Nested structure
score: number;
}// API response types (match actual backend)
interface ApiSearchResult {
id: string; // ✅ Flat structure
commonName: string;
score: number;
}
// Transform at boundary
function transformResults(apiResults: ApiSearchResult[]): AppResult[] {
return apiResults.map(api => ({
bird: { id: api.id, commonName: api.commonName },
score: api.score
}));
}Api*# Compiled outputs
dist/
build/
*.js # ⚠️ CRITICAL in src/ directories
*.js.map
*.jsx
*.jsx.map
# Exception: config files (use negation)
!vite.config.js
!playwright.config.js
!*.config.js# Check for stale .js in src/
find src/ -name "*.js" -type f | grep -v node_modules
# Should return empty or only intentional .js files{
"scripts": {
"clean": "find src/ -name '*.js' -o -name '*.jsx' | xargs rm -f",
"prebuild": "npm run clean",
"predev": "npm run clean"
}
}cd backend && npm run devcd frontend && npm run devcurl -I -X OPTIONS http://localhost:3001/api/v1/search \
-H "Origin: http://localhost:5175" \
-H "Access-Control-Request-Method: POST"
# Should return: Access-Control-Allow-Origin: http://localhost:5175curl -X POST http://localhost:3001/api/v1/search \
-H "Content-Type: application/json" \
-d '{"query": "test"}' | jq '.' > actual-response.json
# Verify response structure matches frontend types| Symptom | Root Cause | Fix |
|---|---|---|
| CORS misconfiguration | Add frontend port to backend CORS_ORIGIN |
| Single port in CORS, frontend on different port | Use comma-separated port list |
| Type mismatch (nested vs flat) | Create API-specific types at boundary |
| Old UI showing after code changes | Stale .js files shadowing .tsx | Remove compiled artifacts from src/ |
| Port already in use | Previous dev server still running | |
.envCORS_ORIGIN.js.jsxfrontend/src/backend/src/curl// e2e/integration-smoke.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Full Stack Integration', () => {
test.beforeAll(async () => {
// Verify backend is running
const health = await fetch('http://localhost:3001/health');
expect(health.ok).toBeTruthy();
});
test('user can complete primary user flow', async ({ page }) => {
// Monitor console for errors
const consoleErrors: string[] = [];
page.on('console', msg => {
if (msg.type() === 'error') {
consoleErrors.push(msg.text());
}
});
// Execute user flow
await page.goto('http://localhost:5173');
await page.getByPlaceholder('Search...').fill('test query');
await page.getByRole('button', { name: 'Search' }).click();
// Verify success
await expect(page.getByTestId('results')).toBeVisible();
// Assert no integration errors
expect(consoleErrors).toHaveLength(0);
expect(consoleErrors.filter(e => e.includes('CORS'))).toHaveLength(0);
expect(consoleErrors.filter(e => e.includes('undefined'))).toHaveLength(0);
});
});