Webdesign 14 Min. Lesezeit

Netlify Functions als Serverless-Backend: Runtime, Secrets & Integrationen

Netlify Functions produktiv einsetzen: Runtime-Eigenheiten, Secrets-Management, Observability mit Netlify Logs und praktische Integrationen mit Brevo und Stripe.

Netlify Functions sind das bevorzugte Backend-Werkzeug für Astro-Projekte auf Netlify, wenn man keinen vollständigen Server-Stack betreiben will. Sie laufen als AWS Lambda-Functions im Hintergrund, integrieren sich nahtlos in den Deploy-Workflow und haben nach der Einrichtung wenig Betriebsaufwand. Dieser Artikel zeigt, worauf man in der Praxis achten muss.

Grundstruktur und Typen

Netlify unterscheidet drei Function-Typen:

TypPfadAuslöser
Synchrone Functionsnetlify/functions/HTTP-Request an /.netlify/functions/<name>
Background Functionsnetlify/functions/ (mit -background Suffix)HTTP-Request, aber asynchron — kein Response erwartet
Edge Functionsnetlify/edge-functions/HTTP-Request, läuft am Edge-Node

Für die meisten Anwendungsfälle — Formulare, E-Mail-Versand, Webhook-Handler — reichen synchrone Functions.

netlify/
└── functions/
    ├── contact.ts          # Synchron: POST /kontakt
    ├── newsletter.ts       # Synchron: POST /newsletter/subscribe
    └── stripe-webhook.ts   # Synchron: POST /stripe/webhook

Handler-Struktur in TypeScript

// netlify/functions/contact.ts
import type { Handler, HandlerEvent, HandlerContext } from '@netlify/functions';

export const handler: Handler = async (event: HandlerEvent, _context: HandlerContext) => {
  // CORS-Preflight
  if (event.httpMethod === 'OPTIONS') {
    return {
      statusCode: 204,
      headers: corsHeaders(),
    };
  }

  if (event.httpMethod !== 'POST') {
    return { statusCode: 405, body: 'Method Not Allowed' };
  }

  const body = JSON.parse(event.body ?? '{}');

  // Eingabe validieren
  if (!isValidContactForm(body)) {
    return {
      statusCode: 400,
      headers: corsHeaders(),
      body: JSON.stringify({ error: 'Ungültige Eingabe' }),
    };
  }

  try {
    await sendContactEmail(body);
    return {
      statusCode: 200,
      headers: { ...corsHeaders(), 'Content-Type': 'application/json' },
      body: JSON.stringify({ success: true }),
    };
  } catch (err) {
    console.error('Fehler beim E-Mail-Versand:', err);
    return {
      statusCode: 500,
      headers: corsHeaders(),
      body: JSON.stringify({ error: 'Interner Fehler' }),
    };
  }
};

function corsHeaders() {
  return {
    'Access-Control-Allow-Origin': process.env.SITE_URL ?? '*',
    'Access-Control-Allow-Headers': 'Content-Type',
    'Access-Control-Allow-Methods': 'POST, OPTIONS',
  };
}

Secrets-Management

Umgebungsvariablen werden in der Netlify-UI unter “Site configuration > Environment variables” gesetzt. Sie sind im Function-Code über process.env verfügbar.

Wichtige Regeln:

  1. Niemals API-Keys in den Quellcode einbetten
  2. Lokale Entwicklung: .env.local (in .gitignore eingetragen)
  3. Production: Netlify-Umgebungsvariablen, nicht .env im Repository
// Secrets aus process.env lesen
const brevoApiKey = process.env.BREVO_API_KEY;
const stripeSecret = process.env.STRIPE_SECRET_KEY;

if (!brevoApiKey) {
  throw new Error('BREVO_API_KEY fehlt in den Umgebungsvariablen');
}

Für lokale Entwicklung mit netlify dev:

# .env.local (gitignored)
BREVO_API_KEY=xkeysib-...
STRIPE_SECRET_KEY=sk_test_...
SITE_URL=http://localhost:4321
# Netlify CLI startet Function-Runtime + Astro-Dev-Server
netlify dev

Brevo-Integration: Newsletter-Anmeldung

// netlify/functions/newsletter.ts
import type { Handler } from '@netlify/functions';

interface BrevoContact {
  email: string;
  listIds: number[];
  updateEnabled: boolean;
  attributes?: Record<string, string>;
}

export const handler: Handler = async (event) => {
  if (event.httpMethod !== 'POST') {
    return { statusCode: 405, body: 'Method Not Allowed' };
  }

  const { email } = JSON.parse(event.body ?? '{}');

  if (!email || !isValidEmail(email)) {
    return {
      statusCode: 400,
      body: JSON.stringify({ error: 'Gültige E-Mail erforderlich' }),
    };
  }

  const contact: BrevoContact = {
    email,
    listIds: [Number(process.env.BREVO_LIST_ID)],
    updateEnabled: false,
    attributes: {
      OPT_IN_SOURCE: 'wendermedia.info',
    },
  };

  const response = await fetch('https://api.brevo.com/v3/contacts', {
    method: 'POST',
    headers: {
      'accept': 'application/json',
      'content-type': 'application/json',
      'api-key': process.env.BREVO_API_KEY!,
    },
    body: JSON.stringify(contact),
  });

  if (response.status === 204 || response.status === 201) {
    return {
      statusCode: 200,
      body: JSON.stringify({ success: true }),
    };
  }

  // Bereits registriert ist kein Fehler aus Nutzersicht
  if (response.status === 400) {
    const data = await response.json();
    if (data.code === 'duplicate_parameter') {
      return { statusCode: 200, body: JSON.stringify({ success: true, alreadySubscribed: true }) };
    }
  }

  console.error('Brevo API Fehler:', response.status, await response.text());
  return { statusCode: 500, body: JSON.stringify({ error: 'Fehler beim Eintragen' }) };
};

function isValidEmail(email: string): boolean {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

Stripe-Webhook: Signatur-Validierung

Stripe sendet Webhooks für Zahlungsereignisse. Die Signatur muss validiert werden — ohne Validierung kann jeder beliebige Daten an den Endpunkt senden.

// netlify/functions/stripe-webhook.ts
import Stripe from 'stripe';
import type { Handler } from '@netlify/functions';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;

export const handler: Handler = async (event) => {
  const sig = event.headers['stripe-signature'];

  if (!sig || !event.body) {
    return { statusCode: 400, body: 'Fehlende Signatur oder Body' };
  }

  let stripeEvent: Stripe.Event;
  try {
    stripeEvent = stripe.webhooks.constructEvent(event.body, sig, webhookSecret);
  } catch (err) {
    console.error('Ungültige Stripe-Signatur:', err);
    return { statusCode: 400, body: 'Ungültige Signatur' };
  }

  switch (stripeEvent.type) {
    case 'payment_intent.succeeded': {
      const paymentIntent = stripeEvent.data.object as Stripe.PaymentIntent;
      await fulfillOrder(paymentIntent);
      break;
    }
    case 'customer.subscription.deleted': {
      const subscription = stripeEvent.data.object as Stripe.Subscription;
      await cancelSubscription(subscription);
      break;
    }
  }

  return { statusCode: 200, body: JSON.stringify({ received: true }) };
};

Wichtig für Stripe-Webhooks: Netlify muss den rohen Request-Body empfangen, nicht einen geparsten JSON. Der @netlify/functions-Handler liefert event.body als String — perfekt für die Signatur-Validierung.

Observability: Logs und Monitoring

Netlify stellt Function-Logs in der Web-UI unter “Functions” bereit. Für strukturiertes Logging:

function log(level: 'info' | 'warn' | 'error', message: string, data?: unknown) {
  const entry = {
    timestamp: new Date().toISOString(),
    level,
    message,
    ...(data ? { data } : {}),
  };
  console[level](JSON.stringify(entry));
}

Strukturierte JSON-Logs sind leichter nach Fehlern zu filtern als freie Strings. Netlify zeigt die letzten 100 Einträge im UI — für tiefere Analyse empfiehlt sich ein Log-Aggregator (DataDog, Axiom, Logtail).

Rate Limiting und Absicherung

Netlify Functions haben kein eingebautes Rate Limiting. Für öffentliche Endpunkte (Formulare, Newsletter) sollte man mindestens ein einfaches IP-basiertes Throttling implementieren:

// Einfacher In-Memory-Cache — bei Lambda-Instanz-Neustart zurückgesetzt
const requestCounts = new Map<string, { count: number; resetAt: number }>();

function isRateLimited(ip: string, maxRequests = 5, windowMs = 60_000): boolean {
  const now = Date.now();
  const entry = requestCounts.get(ip);

  if (!entry || entry.resetAt < now) {
    requestCounts.set(ip, { count: 1, resetAt: now + windowMs });
    return false;
  }

  if (entry.count >= maxRequests) return true;
  entry.count++;
  return false;
}

Für produktionsreifes Rate Limiting bei hohem Traffic: Netlify Edge Functions mit Key-Value-Store oder ein dedizierter Rate-Limiting-Service (Upstash Redis).

Weiterführende Artikel


Serverless Backend für Ihr Astro-Projekt? Wender Media richtet Netlify Functions, Webhooks und API-Integrationen professionell ein — info@wendermedia.info.

Individuelle Beratung gewünscht?

Wender Media unterstützt Sie bei der praktischen Umsetzung — von der technischen Konzeption bis zum Launch. Schreiben Sie uns, wir antworten innerhalb von 24 Stunden.

Jetzt Beratung anfragen

Kostenlos & unverbindlich — info@wendermedia.info

Verwandte Artikel