| name | api-proxy-endpoint |
| description | Create serverless API proxy endpoints that hide API keys and provide a unified backend for the dashboard frontend. Designed for Vercel deployment. |
API Proxy Endpoint Pattern
External APIs often require API keys that must not be exposed in frontend code. Create serverless proxy endpoints that:
- Hide API keys on the server side
- Provide a unified
/api/* namespace for the frontend
- Handle CORS, rate limiting, and error wrapping
Endpoint Structure
Each API endpoint is a file in the api/ directory:
api/
├── stocks.ts # Stock market data proxy
├── news.ts # News API proxy
├── calendar.ts # Calendar events proxy
└── _cors.ts # Shared CORS helper
CORS Helper
export function corsHeaders() {
return {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
};
}
export function handleCors(req: Request): Response | null {
if (req.method === 'OPTIONS') {
return new Response(null, { status: 204, headers: corsHeaders() });
}
return null;
}
Example: Stock Proxy Endpoint
import type { VercelRequest, VercelResponse } from '@vercel/node';
export default async function handler(req: VercelRequest, res: VercelResponse) {
res.setHeader('Access-Control-Allow-Origin', '*');
if (req.method === 'OPTIONS') return res.status(204).end();
const symbols = (req.query.symbols as string || '').split(',').filter(Boolean);
if (symbols.length === 0) {
return res.status(400).json({ error: 'Missing symbols parameter' });
}
const apiKey = process.env.FINNHUB_API_KEY;
if (!apiKey) {
return res.status().({ : });
}
{
quotes = .(
symbols.( (sym) => {
resp = (
);
(!resp.) ();
data = resp.();
{
: sym,
: data.,
: data.,
: data.,
: data.,
: data.,
: data.,
};
})
);
res.(, );
res.({ quotes });
} (err) {
.(, err);
res.().({ : });
}
}
Example: News Proxy Endpoint
import type { VercelRequest, VercelResponse } from '@vercel/node';
export default async function handler(req: VercelRequest, res: VercelResponse) {
res.setHeader('Access-Control-Allow-Origin', '*');
if (req.method === 'OPTIONS') return res.status(204).end();
const apiKey = process.env.NEWS_API_KEY;
if (!apiKey) return res.status(500).json({ error: 'API key not configured' });
const query = req.query.q as string || '';
const category = req.query.category as string || 'general';
const lang = req.query.lang as string || ;
{
url = query
?
: ;
resp = (url);
(!resp.) ();
data = resp.();
res.(, );
res.({
: (data. || []).( ({
: a.,
: a.,
: a.,
: a.?. || ,
: a.,
: a.,
})),
});
} (err) {
.(, err);
res.().({ : });
}
}
Key Patterns
- One file per API domain in the
api/ directory
- Always set CORS headers — frontend runs on different origin during dev
- Environment variables for API keys (
process.env.FINNHUB_API_KEY)
- Cache-Control headers for edge caching (Vercel CDN)
- Error wrapping — return structured JSON errors, never raw upstream errors
- Input validation — validate query parameters before calling upstream
- Typed responses — keep response shapes consistent for frontend consumption
Local Development
During vite dev, configure a proxy in vite.config.ts:
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
},
},
},
});
Or use vercel dev to run serverless functions locally.