Netlify Functions
Platform
Netlify Functions
Overview
Netlify Functions is a serverless function execution environment optimized for JAMstack architecture, enabling end-to-end web application development seamlessly integrated with static site hosting. It provides two execution environments: traditional Node.js Functions and innovative Edge Functions (Deno Runtime), strongly supporting frontend-centric development workflows from static content delivery to API development, authentication, and real-time processing. As of 2025, with edge computing, streaming, TypeScript support, and GitOps workflow integration, it's rapidly growing as the new standard platform for modern web application development.
Details
Netlify Functions 2025 edition provides a serverless computing environment closely integrated with static site hosting as the core of the JAMstack ecosystem. Particularly noteworthy is the introduction of Edge Functions based on Deno Runtime, achieving ultra-high-speed execution of 50-200ms or less at global edge locations, TypeScript native support, Web Standards-compliant APIs, and streaming capabilities. With Git-linked automatic deployment, preview deployments, A/B testing features, long-running processing via Background Functions, and integrated monitoring and logging capabilities, it provides a complete workflow from development to production.
Key Features
- Edge Functions: Ultra-fast edge execution (under 50ms) via Deno Runtime
- Node.js Functions: Traditional serverless functions (up to 15 minutes execution)
- JAMstack Integration: Seamless integration with static sites
- TypeScript Support: Native TypeScript support
- Git Integration: Automatic deployment via GitOps workflows
- Streaming: Real-time response streaming
Latest 2025 Features
- Edge Functions: High-speed execution under 50ms with streaming support
- Background Functions: Long-running asynchronous execution up to 15 minutes
- Enhanced Deploy Previews: Preview environments for each branch
- Integrated Monitoring: Real-time function monitoring and log analysis
- Framework Integration: Deep integration with Astro, Next.js, Nuxt, SvelteKit, etc.
Advantages and Disadvantages
Advantages
- Integrated development experience specialized for JAMstack architecture
- World-class low-latency execution environment via Edge Functions
- Complete automation of development workflows through Git integration
- Enhanced developer experience with TypeScript/JavaScript dual support
- Unified management of static hosting and API development
- Safe development process through preview deployments
- Rich framework integration and templates
Disadvantages
- Strong dependency on Netlify platform and vendor lock-in
- Processing constraints due to Edge Functions execution time limit (50ms)
- Limited backend functionality compared to other cloud providers
- Unsuitable for complex database operations or heavy processing
- Lack of advanced operational features at enterprise level
- Potential unexpected cost increases due to pay-per-use billing
Reference Pages
Code Examples
Setup and Function Creation
# Install Netlify CLI
npm install -g netlify-cli
# Login to Netlify
netlify login
# Initialize new project
netlify init
# Start local development server
netlify dev
# Create function
netlify functions:create
// netlify/functions/hello.js - Basic Node.js function
exports.handler = async (event, context) => {
console.log('Event:', JSON.stringify(event, null, 2));
console.log('Context:', JSON.stringify(context, null, 2));
// CORS headers setup
const headers = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Content-Type': 'application/json'
};
// Handle preflight request
if (event.httpMethod === 'OPTIONS') {
return {
statusCode: 200,
headers,
body: ''
};
}
try {
const { httpMethod, queryStringParameters, body, headers: requestHeaders } = event;
const response = {
message: 'Hello from Netlify Functions!',
timestamp: new Date().toISOString(),
method: httpMethod,
query: queryStringParameters || {},
userAgent: requestHeaders['user-agent'] || 'Unknown',
clientIp: event.headers['client-ip'] || 'Unknown'
};
return {
statusCode: 200,
headers,
body: JSON.stringify(response, null, 2)
};
} catch (error) {
console.error('Function error:', error);
return {
statusCode: 500,
headers,
body: JSON.stringify({
error: 'Internal server error',
message: error.message
})
};
}
};
// netlify/edge-functions/hello-edge.ts - Edge Function (TypeScript)
import { Config } from "https://edge.netlify.com";
export default async (request: Request, context: any): Promise<Response> => {
const url = new URL(request.url);
const name = url.searchParams.get("name") || "World";
const userAgent = request.headers.get("user-agent") || "Unknown";
const country = context.geo?.country?.name || "Unknown";
// Create response data
const responseData = {
message: `Hello, ${name}!`,
timestamp: new Date().toISOString(),
userAgent,
country,
method: request.method,
path: url.pathname,
query: Object.fromEntries(url.searchParams.entries())
};
// Create JSON response
const response = new Response(JSON.stringify(responseData, null, 2), {
status: 200,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
"Cache-Control": "public, max-age=60"
}
});
return response;
};
export const config: Config = {
path: "/api/hello-edge"
};
HTTP APIs and Request Handling
// netlify/functions/users.ts - RESTful API implementation
import { Handler, HandlerEvent, HandlerContext } from '@netlify/functions';
interface User {
id: string;
name: string;
email: string;
status: 'active' | 'inactive';
createdAt: string;
updatedAt: string;
}
// Demo memory store (use database in production)
let users: User[] = [
{
id: '1',
name: 'John Doe',
email: '[email protected]',
status: 'active',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
}
];
const handler: Handler = async (event: HandlerEvent, context: HandlerContext) => {
const { httpMethod, queryStringParameters, body, path } = event;
const headers = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Content-Type': 'application/json'
};
if (httpMethod === 'OPTIONS') {
return { statusCode: 200, headers, body: '' };
}
try {
switch (httpMethod) {
case 'GET':
// User list or filtered search
const status = queryStringParameters?.status;
const search = queryStringParameters?.search;
let filteredUsers = users;
if (status) {
filteredUsers = filteredUsers.filter(user => user.status === status);
}
if (search) {
filteredUsers = filteredUsers.filter(user =>
user.name.toLowerCase().includes(search.toLowerCase()) ||
user.email.toLowerCase().includes(search.toLowerCase())
);
}
return {
statusCode: 200,
headers,
body: JSON.stringify({
users: filteredUsers,
count: filteredUsers.length,
filters: { status, search }
})
};
case 'POST':
// Create new user
if (!body) {
return {
statusCode: 400,
headers,
body: JSON.stringify({ error: 'Request body is required' })
};
}
const userData = JSON.parse(body);
if (!userData.name || !userData.email) {
return {
statusCode: 400,
headers,
body: JSON.stringify({ error: 'Name and email are required' })
};
}
const newUser: User = {
id: Date.now().toString(),
name: userData.name,
email: userData.email,
status: 'active',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
users.push(newUser);
return {
statusCode: 201,
headers,
body: JSON.stringify({
message: 'User created successfully',
user: newUser
})
};
case 'PUT':
// Update user information
if (!body) {
return {
statusCode: 400,
headers,
body: JSON.stringify({ error: 'Request body is required' })
};
}
const updateData = JSON.parse(body);
const userId = queryStringParameters?.id;
if (!userId) {
return {
statusCode: 400,
headers,
body: JSON.stringify({ error: 'User ID is required' })
};
}
const userIndex = users.findIndex(user => user.id === userId);
if (userIndex === -1) {
return {
statusCode: 404,
headers,
body: JSON.stringify({ error: 'User not found' })
};
}
users[userIndex] = {
...users[userIndex],
...updateData,
updatedAt: new Date().toISOString()
};
return {
statusCode: 200,
headers,
body: JSON.stringify({
message: 'User updated successfully',
user: users[userIndex]
})
};
case 'DELETE':
// Delete user
const deleteUserId = queryStringParameters?.id;
if (!deleteUserId) {
return {
statusCode: 400,
headers,
body: JSON.stringify({ error: 'User ID is required' })
};
}
const deleteIndex = users.findIndex(user => user.id === deleteUserId);
if (deleteIndex === -1) {
return {
statusCode: 404,
headers,
body: JSON.stringify({ error: 'User not found' })
};
}
users.splice(deleteIndex, 1);
return {
statusCode: 200,
headers,
body: JSON.stringify({ message: 'User deleted successfully' })
};
default:
return {
statusCode: 405,
headers,
body: JSON.stringify({ error: 'Method not allowed' })
};
}
} catch (error) {
console.error('API Error:', error);
return {
statusCode: 500,
headers,
body: JSON.stringify({
error: 'Internal server error',
message: error instanceof Error ? error.message : 'Unknown error'
})
};
}
};
export { handler };
// netlify/edge-functions/api-proxy.ts - Edge Function API proxy
import { Config } from "https://edge.netlify.com";
export default async (request: Request, context: any): Promise<Response> => {
const url = new URL(request.url);
const apiPath = url.pathname.replace('/api/proxy', '');
// Rate limiting implementation
const clientIp = request.headers.get('CF-Connecting-IP') ||
request.headers.get('X-Forwarded-For') ||
'unknown';
const rateLimitKey = `rate_limit_${clientIp}`;
const requestCount = parseInt(context.cookies.get(rateLimitKey) || '0');
if (requestCount > 100) { // 100 requests per minute limit
return new Response(JSON.stringify({
error: 'Rate limit exceeded',
limit: 100,
window: '1 minute'
}), {
status: 429,
headers: {
'Content-Type': 'application/json',
'Retry-After': '60'
}
});
}
try {
// Proxy request to external API
const externalApiUrl = `https://api.external-service.com${apiPath}`;
const proxyRequest = new Request(externalApiUrl, {
method: request.method,
headers: {
'Authorization': `Bearer ${Deno.env.get('EXTERNAL_API_KEY')}`,
'Content-Type': 'application/json',
'User-Agent': 'Netlify-Edge-Proxy/1.0'
},
body: request.method !== 'GET' ? await request.text() : undefined
});
const response = await fetch(proxyRequest);
const data = await response.text();
// Transform/filter response
let transformedData = data;
if (response.headers.get('content-type')?.includes('application/json')) {
const jsonData = JSON.parse(data);
// Remove sensitive information
delete jsonData.internal_id;
delete jsonData.api_key;
transformedData = JSON.stringify(jsonData);
}
// Update rate limit counter
const newCount = requestCount + 1;
const responseHeaders = new Headers(response.headers);
responseHeaders.set('Set-Cookie', `${rateLimitKey}=${newCount}; Max-Age=60; HttpOnly`);
responseHeaders.set('X-Rate-Limit-Remaining', (100 - newCount).toString());
return new Response(transformedData, {
status: response.status,
headers: responseHeaders
});
} catch (error) {
console.error('Proxy error:', error);
return new Response(JSON.stringify({
error: 'Proxy request failed',
message: error instanceof Error ? error.message : 'Unknown error'
}), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
};
export const config: Config = {
path: "/api/proxy/*"
};
Database Integration and Data Processing
// netlify/functions/database-users.ts - Supabase integration
import { Handler } from '@netlify/functions';
import { createClient } from '@supabase/supabase-js';
const supabaseUrl = process.env.SUPABASE_URL!;
const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY!;
const supabase = createClient(supabaseUrl, supabaseServiceKey);
interface UserRecord {
id?: string;
name: string;
email: string;
status: 'active' | 'inactive';
profile?: {
avatar_url?: string;
bio?: string;
website?: string;
};
created_at?: string;
updated_at?: string;
}
const handler: Handler = async (event) => {
const { httpMethod, queryStringParameters, body } = event;
const headers = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Content-Type': 'application/json'
};
if (httpMethod === 'OPTIONS') {
return { statusCode: 200, headers, body: '' };
}
try {
switch (httpMethod) {
case 'GET':
// Get user list (with pagination)
const page = parseInt(queryStringParameters?.page || '1');
const limit = parseInt(queryStringParameters?.limit || '10');
const search = queryStringParameters?.search;
const status = queryStringParameters?.status;
let query = supabase
.from('users')
.select('*, profiles(*)', { count: 'exact' })
.range((page - 1) * limit, page * limit - 1)
.order('created_at', { ascending: false });
if (search) {
query = query.or(`name.ilike.%${search}%,email.ilike.%${search}%`);
}
if (status) {
query = query.eq('status', status);
}
const { data: users, error, count } = await query;
if (error) {
throw error;
}
return {
statusCode: 200,
headers,
body: JSON.stringify({
users,
pagination: {
total: count,
page,
limit,
pages: Math.ceil((count || 0) / limit)
}
})
};
case 'POST':
// Create new user
if (!body) {
return {
statusCode: 400,
headers,
body: JSON.stringify({ error: 'Request body is required' })
};
}
const userData: UserRecord = JSON.parse(body);
// Validation
if (!userData.name || !userData.email) {
return {
statusCode: 400,
headers,
body: JSON.stringify({
error: 'Name and email are required'
})
};
}
// Check email duplicate
const { data: existingUser } = await supabase
.from('users')
.select('id')
.eq('email', userData.email)
.single();
if (existingUser) {
return {
statusCode: 409,
headers,
body: JSON.stringify({
error: 'Email already exists'
})
};
}
// Create user
const { data: newUser, error: createError } = await supabase
.from('users')
.insert([{
name: userData.name,
email: userData.email,
status: userData.status || 'active'
}])
.select()
.single();
if (createError) {
throw createError;
}
// Create profile (optional)
if (userData.profile && newUser) {
await supabase
.from('profiles')
.insert([{
user_id: newUser.id,
...userData.profile
}]);
}
return {
statusCode: 201,
headers,
body: JSON.stringify({
message: 'User created successfully',
user: newUser
})
};
case 'PUT':
// Update user information
const userId = queryStringParameters?.id;
if (!userId) {
return {
statusCode: 400,
headers,
body: JSON.stringify({ error: 'User ID is required' })
};
}
if (!body) {
return {
statusCode: 400,
headers,
body: JSON.stringify({ error: 'Request body is required' })
};
}
const updateData: Partial<UserRecord> = JSON.parse(body);
const { data: updatedUser, error: updateError } = await supabase
.from('users')
.update({
name: updateData.name,
email: updateData.email,
status: updateData.status,
updated_at: new Date().toISOString()
})
.eq('id', userId)
.select()
.single();
if (updateError) {
if (updateError.code === 'PGRST116') {
return {
statusCode: 404,
headers,
body: JSON.stringify({ error: 'User not found' })
};
}
throw updateError;
}
return {
statusCode: 200,
headers,
body: JSON.stringify({
message: 'User updated successfully',
user: updatedUser
})
};
case 'DELETE':
// Delete user
const deleteUserId = queryStringParameters?.id;
if (!deleteUserId) {
return {
statusCode: 400,
headers,
body: JSON.stringify({ error: 'User ID is required' })
};
}
const { error: deleteError } = await supabase
.from('users')
.delete()
.eq('id', deleteUserId);
if (deleteError) {
throw deleteError;
}
return {
statusCode: 200,
headers,
body: JSON.stringify({
message: 'User deleted successfully'
})
};
default:
return {
statusCode: 405,
headers,
body: JSON.stringify({ error: 'Method not allowed' })
};
}
} catch (error) {
console.error('Database error:', error);
return {
statusCode: 500,
headers,
body: JSON.stringify({
error: 'Database operation failed',
message: error instanceof Error ? error.message : 'Unknown error'
})
};
}
};
export { handler };
// netlify/functions/analytics.ts - Analytics data processing
import { Handler } from '@netlify/functions';
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
interface AnalyticsEvent {
event_type: string;
user_id?: string;
session_id: string;
page_url?: string;
properties: Record<string, any>;
timestamp: string;
user_agent?: string;
ip_address?: string;
}
const handler: Handler = async (event) => {
if (event.httpMethod !== 'POST') {
return {
statusCode: 405,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ error: 'Method not allowed' })
};
}
try {
const events: AnalyticsEvent[] = Array.isArray(JSON.parse(event.body || '[]'))
? JSON.parse(event.body || '[]')
: [JSON.parse(event.body || '{}')];
// Event validation and normalization
const validatedEvents = events.map(evt => ({
...evt,
timestamp: evt.timestamp || new Date().toISOString(),
user_agent: event.headers['user-agent'] || evt.user_agent,
ip_address: event.headers['client-ip'] || evt.ip_address,
properties: evt.properties || {}
}));
// Batch insert to database
const { error: insertError } = await supabase
.from('analytics_events')
.insert(validatedEvents);
if (insertError) {
throw insertError;
}
// Update real-time aggregations
await Promise.all(validatedEvents.map(async (evt) => {
switch (evt.event_type) {
case 'page_view':
await updatePageViewCounters(evt);
break;
case 'user_interaction':
await updateInteractionMetrics(evt);
break;
case 'conversion':
await updateConversionFunnels(evt);
break;
}
}));
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: 'Events processed successfully',
count: validatedEvents.length
})
};
} catch (error) {
console.error('Analytics error:', error);
return {
statusCode: 500,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
error: 'Failed to process analytics events',
message: error instanceof Error ? error.message : 'Unknown error'
})
};
}
};
async function updatePageViewCounters(event: AnalyticsEvent) {
if (!event.page_url) return;
const { error } = await supabase
.from('page_analytics')
.upsert({
page_url: event.page_url,
view_count: 1,
last_viewed: event.timestamp
}, {
onConflict: 'page_url',
ignoreDuplicates: false
});
if (error) {
console.error('Failed to update page view counters:', error);
}
}
async function updateInteractionMetrics(event: AnalyticsEvent) {
if (!event.user_id) return;
const { error } = await supabase
.from('user_interactions')
.upsert({
user_id: event.user_id,
interaction_type: event.properties.interaction_type,
interaction_count: 1,
last_interaction: event.timestamp
}, {
onConflict: 'user_id,interaction_type',
ignoreDuplicates: false
});
if (error) {
console.error('Failed to update interaction metrics:', error);
}
}
async function updateConversionFunnels(event: AnalyticsEvent) {
const { error } = await supabase
.from('conversion_events')
.insert({
user_id: event.user_id,
session_id: event.session_id,
funnel_step: event.properties.step,
conversion_value: event.properties.value || 0,
timestamp: event.timestamp
});
if (error) {
console.error('Failed to track conversion:', error);
}
}
export { handler };
Authentication and Security
// netlify/functions/auth-login.ts - JWT authentication
import { Handler } from '@netlify/functions';
import { createClient } from '@supabase/supabase-js';
import * as bcrypt from 'bcryptjs';
import * as jwt from 'jsonwebtoken';
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
const JWT_SECRET = process.env.JWT_SECRET!;
const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '7d';
const handler: Handler = async (event) => {
if (event.httpMethod !== 'POST') {
return {
statusCode: 405,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ error: 'Method not allowed' })
};
}
try {
const { email, password } = JSON.parse(event.body || '{}');
if (!email || !password) {
return {
statusCode: 400,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
error: 'Email and password are required'
})
};
}
// Find user
const { data: user, error } = await supabase
.from('users')
.select('id, email, password_hash, name, status, role')
.eq('email', email)
.single();
if (error || !user) {
return {
statusCode: 401,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ error: 'Invalid credentials' })
};
}
// Verify password
const isValidPassword = await bcrypt.compare(password, user.password_hash);
if (!isValidPassword) {
return {
statusCode: 401,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ error: 'Invalid credentials' })
};
}
// Check active user
if (user.status !== 'active') {
return {
statusCode: 403,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ error: 'Account is not active' })
};
}
// Generate JWT token
const token = jwt.sign(
{
userId: user.id,
email: user.email,
role: user.role
},
JWT_SECRET,
{ expiresIn: JWT_EXPIRES_IN }
);
// Generate refresh token
const refreshToken = jwt.sign(
{ userId: user.id },
JWT_SECRET + 'refresh',
{ expiresIn: '30d' }
);
// Update last login time
await supabase
.from('users')
.update({ last_login_at: new Date().toISOString() })
.eq('id', user.id);
// Set secure cookies
const cookieOptions = [
`token=${token}; HttpOnly; Secure; SameSite=Strict; Max-Age=604800; Path=/`,
`refreshToken=${refreshToken}; HttpOnly; Secure; SameSite=Strict; Max-Age=2592000; Path=/`
];
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Set-Cookie': cookieOptions
},
body: JSON.stringify({
message: 'Login successful',
user: {
id: user.id,
name: user.name,
email: user.email,
role: user.role
}
})
};
} catch (error) {
console.error('Login error:', error);
return {
statusCode: 500,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
error: 'Authentication failed',
message: error instanceof Error ? error.message : 'Unknown error'
})
};
}
};
export { handler };
// netlify/edge-functions/auth-middleware.ts - Authentication middleware
import { Config } from "https://edge.netlify.com";
import { verify } from "https://deno.land/x/[email protected]/mod.ts";
interface JWTPayload {
userId: string;
email: string;
role: string;
iat: number;
exp: number;
}
export default async (request: Request, context: any): Promise<Response> => {
const url = new URL(request.url);
// Skip public routes
const publicPaths = ['/api/auth', '/api/public', '/api/health'];
if (publicPaths.some(path => url.pathname.startsWith(path))) {
return context.next();
}
// Check protected routes
const protectedPaths = ['/api/admin', '/api/user', '/api/protected'];
const requiresAuth = protectedPaths.some(path => url.pathname.startsWith(path));
if (!requiresAuth) {
return context.next();
}
try {
// Get token
const cookies = parseCookies(request.headers.get('cookie') || '');
const token = cookies.token || request.headers.get('authorization')?.replace('Bearer ', '');
if (!token) {
return new Response(JSON.stringify({
error: 'Authentication required'
}), {
status: 401,
headers: { 'Content-Type': 'application/json' }
});
}
// Verify token
const jwtSecret = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(Deno.env.get('JWT_SECRET')),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['verify']
);
const payload = await verify(token, jwtSecret) as JWTPayload;
// Role-based access control
if (url.pathname.startsWith('/api/admin') && payload.role !== 'admin') {
return new Response(JSON.stringify({
error: 'Insufficient permissions'
}), {
status: 403,
headers: { 'Content-Type': 'application/json' }
});
}
// Add user info to headers
const requestHeaders = new Headers(request.headers);
requestHeaders.set('x-user-id', payload.userId);
requestHeaders.set('x-user-email', payload.email);
requestHeaders.set('x-user-role', payload.role);
const newRequest = new Request(request.url, {
method: request.method,
headers: requestHeaders,
body: request.body
});
return context.next(newRequest);
} catch (error) {
console.error('Auth middleware error:', error);
return new Response(JSON.stringify({
error: 'Invalid token'
}), {
status: 401,
headers: { 'Content-Type': 'application/json' }
});
}
};
function parseCookies(cookieHeader: string): Record<string, string> {
const cookies: Record<string, string> = {};
cookieHeader.split(';').forEach(cookie => {
const [name, value] = cookie.trim().split('=');
if (name && value) {
cookies[name] = decodeURIComponent(value);
}
});
return cookies;
}
export const config: Config = {
path: "/api/*"
};
Event-Driven Architecture
// netlify/functions/webhooks-stripe.ts - Stripe Webhook handling
import { Handler } from '@netlify/functions';
import Stripe from 'stripe';
import { createClient } from '@supabase/supabase-js';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2023-10-16',
});
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;
const handler: Handler = async (event) => {
if (event.httpMethod !== 'POST') {
return {
statusCode: 405,
body: JSON.stringify({ error: 'Method not allowed' })
};
}
const sig = event.headers['stripe-signature'];
if (!sig) {
return {
statusCode: 400,
body: JSON.stringify({ error: 'Missing stripe signature' })
};
}
let stripeEvent: Stripe.Event;
try {
stripeEvent = stripe.webhooks.constructEvent(
event.body!,
sig,
webhookSecret
);
} catch (err) {
console.error('Webhook signature verification failed:', err);
return {
statusCode: 400,
body: JSON.stringify({ error: 'Invalid signature' })
};
}
try {
switch (stripeEvent.type) {
case 'checkout.session.completed':
await handleCheckoutCompleted(
stripeEvent.data.object as Stripe.Checkout.Session
);
break;
case 'invoice.payment_succeeded':
await handlePaymentSucceeded(
stripeEvent.data.object as Stripe.Invoice
);
break;
case 'customer.subscription.updated':
await handleSubscriptionUpdated(
stripeEvent.data.object as Stripe.Subscription
);
break;
case 'customer.subscription.deleted':
await handleSubscriptionCanceled(
stripeEvent.data.object as Stripe.Subscription
);
break;
default:
console.log(`Unhandled event type: ${stripeEvent.type}`);
}
return {
statusCode: 200,
body: JSON.stringify({ received: true })
};
} catch (error) {
console.error('Webhook processing error:', error);
return {
statusCode: 500,
body: JSON.stringify({
error: 'Webhook processing failed',
message: error instanceof Error ? error.message : 'Unknown error'
})
};
}
};
async function handleCheckoutCompleted(session: Stripe.Checkout.Session) {
console.log('Processing checkout completion:', session.id);
// Update subscription information
if (session.customer && session.subscription) {
const subscription = await stripe.subscriptions.retrieve(
session.subscription as string
);
// Update user subscription status
await supabase
.from('user_subscriptions')
.upsert({
customer_id: session.customer as string,
subscription_id: subscription.id,
status: subscription.status,
current_period_start: new Date(subscription.current_period_start * 1000),
current_period_end: new Date(subscription.current_period_end * 1000),
updated_at: new Date().toISOString()
});
// Record analytics event
await fetch(`${process.env.URL}/api/analytics`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
event_type: 'conversion',
properties: {
step: 'subscription_created',
value: session.amount_total! / 100,
currency: session.currency,
subscription_id: session.subscription
},
session_id: session.id,
timestamp: new Date().toISOString()
})
});
}
}
async function handlePaymentSucceeded(invoice: Stripe.Invoice) {
console.log('Processing successful payment:', invoice.id);
// Save payment record
await supabase
.from('payments')
.insert({
invoice_id: invoice.id,
customer_id: invoice.customer as string,
amount: invoice.amount_paid,
currency: invoice.currency,
status: 'succeeded',
paid_at: new Date(invoice.status_transitions.paid_at! * 1000),
created_at: new Date().toISOString()
});
}
async function handleSubscriptionUpdated(subscription: Stripe.Subscription) {
console.log('Processing subscription update:', subscription.id);
// Update subscription details
await supabase
.from('user_subscriptions')
.update({
status: subscription.status,
current_period_start: new Date(subscription.current_period_start * 1000),
current_period_end: new Date(subscription.current_period_end * 1000),
cancel_at_period_end: subscription.cancel_at_period_end,
updated_at: new Date().toISOString()
})
.eq('subscription_id', subscription.id);
}
async function handleSubscriptionCanceled(subscription: Stripe.Subscription) {
console.log('Processing subscription cancellation:', subscription.id);
// Update subscription status
await supabase
.from('user_subscriptions')
.update({
status: 'canceled',
canceled_at: new Date().toISOString(),
updated_at: new Date().toISOString()
})
.eq('subscription_id', subscription.id);
}
export { handler };
Monitoring and Performance Optimization
// netlify/functions/health.ts - Health check endpoint
import { Handler } from '@netlify/functions';
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
interface HealthStatus {
status: 'healthy' | 'unhealthy';
timestamp: string;
services: {
database: 'healthy' | 'unhealthy';
external_apis: 'healthy' | 'unhealthy';
};
metrics: {
response_time: number;
memory_usage: number;
function_region: string;
};
}
const handler: Handler = async (event, context) => {
const startTime = Date.now();
try {
// Check database connectivity
const dbHealthy = await checkDatabaseHealth();
// Check external APIs
const externalApisHealthy = await checkExternalApis();
// Calculate metrics
const responseTime = Date.now() - startTime;
const memoryUsage = process.memoryUsage().heapUsed / 1024 / 1024; // MB
const allHealthy = dbHealthy && externalApisHealthy;
const healthStatus: HealthStatus = {
status: allHealthy ? 'healthy' : 'unhealthy',
timestamp: new Date().toISOString(),
services: {
database: dbHealthy ? 'healthy' : 'unhealthy',
external_apis: externalApisHealthy ? 'healthy' : 'unhealthy'
},
metrics: {
response_time: responseTime,
memory_usage: Math.round(memoryUsage * 100) / 100,
function_region: process.env.AWS_REGION || 'unknown'
}
};
const statusCode = allHealthy ? 200 : 503;
return {
statusCode,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(healthStatus)
};
} catch (error) {
console.error('Health check error:', error);
const errorStatus: HealthStatus = {
status: 'unhealthy',
timestamp: new Date().toISOString(),
services: {
database: 'unhealthy',
external_apis: 'unhealthy'
},
metrics: {
response_time: Date.now() - startTime,
memory_usage: 0,
function_region: process.env.AWS_REGION || 'unknown'
}
};
return {
statusCode: 503,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(errorStatus)
};
}
};
async function checkDatabaseHealth(): Promise<boolean> {
try {
const { data, error } = await supabase
.from('health_check')
.select('id')
.limit(1);
return !error;
} catch (error) {
console.error('Database health check failed:', error);
return false;
}
}
async function checkExternalApis(): Promise<boolean> {
try {
const checks = await Promise.allSettled([
fetch('https://api.stripe.com/v1', {
method: 'HEAD',
headers: { 'Authorization': `Bearer ${process.env.STRIPE_SECRET_KEY}` }
}),
// Add other external API checks as needed
]);
return checks.every(result =>
result.status === 'fulfilled' &&
result.value.ok
);
} catch (error) {
console.error('External API health check failed:', error);
return false;
}
}
export { handler };
# netlify.toml - Project configuration file
[build]
publish = "dist"
command = "npm run build"
functions = "netlify/functions"
edge_functions = "netlify/edge-functions"
[build.environment]
NODE_VERSION = "18"
[[redirects]]
from = "/api/*"
to = "/.netlify/functions/:splat"
status = 200
[[edge_functions]]
function = "auth-middleware"
path = "/api/*"
[[edge_functions]]
function = "hello-edge"
path = "/api/hello-edge"
[[edge_functions]]
function = "api-proxy"
path = "/api/proxy/*"
[functions]
node_bundler = "nft"
[functions."auth-login"]
external_node_modules = ["bcryptjs", "jsonwebtoken"]
[functions."database-users"]
external_node_modules = ["@supabase/supabase-js"]
[functions."webhooks-stripe"]
external_node_modules = ["stripe"]
[dev]
command = "npm run dev"
port = 3000
targetPort = 8888
framework = "#static"
[context.production.environment]
NODE_ENV = "production"
[context.deploy-preview.environment]
NODE_ENV = "staging"
[context.branch-deploy.environment]
NODE_ENV = "development"
Netlify Functions provides a serverless platform optimized for JAMstack architecture, enabling full-stack development integrated with static site hosting, efficient development workflows through Git integration, and high-speed Edge Functions, establishing a new standard for modern web application development.