Zurück zum Blog
9 Min. LesezeitInfrastructure

Serverless Architecture in 2025: Building Without Servers

Master serverless architecture with AWS Lambda, Vercel Functions, and Cloudflare Workers. Learn when to go serverless and how to build production-ready applications.

Serverless Architecture in 2025: Building Without Servers

Serverless doesn't mean "no servers"—it means you don't manage them. This paradigm shift allows developers to focus on code while cloud providers handle infrastructure, scaling, and reliability.

What Is Serverless?

Serverless architecture consists of:

  • Functions: Code executed on-demand
  • Managed services: Databases, storage, queues
  • Event-driven: Triggered by events, not always running
  • Pay-per-use: Only pay for actual execution time

Core Concepts

1. Functions as a Service (FaaS)

Deploy individual functions, not entire servers:

// AWS Lambda function
export const handler = async (event) => {
  const { userId } = JSON.parse(event.body);

  const user = await dynamodb.get({
    TableName: 'Users',
    Key: { id: userId },
  });

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

2. Event-Driven Architecture

Functions trigger on events:

// Triggered by S3 upload
export const handler = async (event) => {
  const bucket = event.Records[0].s3.bucket.name;
  const key = event.Records[0].s3.object.key;

  // Process image
  const image = await s3.getObject({ Bucket: bucket, Key: key });
  const thumbnail = await createThumbnail(image);

  // Save thumbnail
  await s3.putObject({
    Bucket: bucket,
    Key: `thumbnails/${key}`,
    Body: thumbnail,
  });
};

3. Stateless Functions

Each invocation is independent:

// ✅ Good - stateless
export const handler = async (event) => {
  const db = await connectToDatabase(); // Fresh connection
  const result = await db.query(event.query);
  return result;
};

// ❌ Bad - stateful (unreliable in serverless)
let cache = {};

export const handler = async (event) => {
  // Cache may not persist between invocations
  if (cache[event.key]) {
    return cache[event.key];
  }
  // ...
};

Popular Serverless Platforms

AWS Lambda

The original serverless platform:

// Lambda with API Gateway
export const handler = async (event) => {
  const method = event.httpMethod;
  const path = event.path;

  if (method === 'GET' && path === '/users') {
    const users = await getUsers();
    return {
      statusCode: 200,
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(users),
    };
  }

  return {
    statusCode: 404,
    body: JSON.stringify({ error: 'Not found' }),
  };
};

Deployment:

# serverless.yml
service: my-api

provider:
  name: aws
  runtime: nodejs18.x

functions:
  api:
    handler: handler.handler
    events:
      - http:
          path: users
          method: get

Vercel Functions

Optimized for frontend frameworks:

// api/users.js
export default async function handler(req, res) {
  if (req.method === 'GET') {
    const users = await db.users.findAll();
    res.status(200).json(users);
  } else {
    res.status(405).json({ error: 'Method not allowed' });
  }
}

Deploy:

vercel --prod

Cloudflare Workers

Edge-based serverless:

// index.js
export default {
  async fetch(request, env) {
    const url = new URL(request.url);

    if (url.pathname === '/api/users') {
      const users = await env.DB.prepare(
        'SELECT * FROM users'
      ).all();

      return Response.json(users.results);
    }

    return new Response('Not found', { status: 404 });
  },
};

Azure Functions

Microsoft's serverless offering:

// function.js
module.exports = async function (context, req) {
  if (req.method === 'GET') {
    const users = await getUsers();
    context.res = {
      status: 200,
      body: users,
    };
  }
};

Building Serverless APIs

REST API Example

// AWS Lambda + API Gateway
// GET /products
export const listProducts = async () => {
  const products = await dynamodb.scan({
    TableName: 'Products',
  });

  return {
    statusCode: 200,
    body: JSON.stringify(products.Items),
  };
};

// GET /products/:id
export const getProduct = async (event) => {
  const { id } = event.pathParameters;

  const product = await dynamodb.get({
    TableName: 'Products',
    Key: { id },
  });

  return {
    statusCode: 200,
    body: JSON.stringify(product.Item),
  };
};

// POST /products
export const createProduct = async (event) => {
  const product = JSON.parse(event.body);

  await dynamodb.put({
    TableName: 'Products',
    Item: {
      id: uuid(),
      ...product,
      createdAt: new Date().toISOString(),
    },
  });

  return {
    statusCode: 201,
    body: JSON.stringify(product),
  };
};

GraphQL API

// Apollo Server Lambda
import { ApolloServer } from '@apollo/server';
import { startServerAndCreateLambdaHandler } from '@as-integrations/aws-lambda';

const typeDefs = `
  type Query {
    users: [User!]!
    user(id: ID!): User
  }

  type User {
    id: ID!
    name: String!
    email: String!
  }
`;

const resolvers = {
  Query: {
    users: async () => {
      const result = await dynamodb.scan({ TableName: 'Users' });
      return result.Items;
    },
    user: async (_, { id }) => {
      const result = await dynamodb.get({
        TableName: 'Users',
        Key: { id },
      });
      return result.Item;
    },
  },
};

const server = new ApolloServer({ typeDefs, resolvers });

export const handler = startServerAndCreateLambdaHandler(server);

Database Options

DynamoDB (AWS)

NoSQL, serverless-native:

import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, PutCommand, GetCommand } from '@aws-sdk/lib-dynamodb';

const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);

// Write
await docClient.send(new PutCommand({
  TableName: 'Users',
  Item: { id: '123', name: 'Alice', email: 'alice@example.com' },
}));

// Read
const result = await docClient.send(new GetCommand({
  TableName: 'Users',
  Key: { id: '123' },
}));

Fauna

Globally distributed, serverless database:

import { Client, query as q } from 'faunadb';

const client = new Client({
  secret: process.env.FAUNA_SECRET,
});

// Create
await client.query(
  q.Create(q.Collection('users'), {
    data: { name: 'Alice', email: 'alice@example.com' },
  })
);

// Query
const users = await client.query(
  q.Map(
    q.Paginate(q.Documents(q.Collection('users'))),
    q.Lambda('ref', q.Get(q.Var('ref')))
  )
);

PlanetScale

Serverless MySQL:

import { connect } from '@planetscale/database';

const conn = connect({
  url: process.env.DATABASE_URL,
});

const users = await conn.execute('SELECT * FROM users');

Supabase

PostgreSQL-based, serverless-friendly:

import { createClient } from '@supabase/supabase-js';

const supabase = createClient(
  process.env.SUPABASE_URL,
  process.env.SUPABASE_KEY
);

const { data, error } = await supabase
  .from('users')
  .select('*')
  .eq('active', true);

Authentication

AWS Cognito

import { CognitoIdentityProviderClient, InitiateAuthCommand } from '@aws-sdk/client-cognito-identity-provider';

const client = new CognitoIdentityProviderClient({});

export const handler = async (event) => {
  const { username, password } = JSON.parse(event.body);

  const command = new InitiateAuthCommand({
    AuthFlow: 'USER_PASSWORD_AUTH',
    ClientId: process.env.COGNITO_CLIENT_ID,
    AuthParameters: {
      USERNAME: username,
      PASSWORD: password,
    },
  });

  const response = await client.send(command);

  return {
    statusCode: 200,
    body: JSON.stringify({
      token: response.AuthenticationResult.AccessToken,
    }),
  };
};

Auth0

import { ManagementClient } from 'auth0';

const auth0 = new ManagementClient({
  domain: process.env.AUTH0_DOMAIN,
  clientId: process.env.AUTH0_CLIENT_ID,
  clientSecret: process.env.AUTH0_CLIENT_SECRET,
});

export const handler = async (event) => {
  const token = event.headers.authorization?.replace('Bearer ', '');

  // Verify token
  const user = await auth0.getUser({ id: decodedToken.sub });

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

Background Jobs

SQS + Lambda

// Producer
import { SQSClient, SendMessageCommand } from '@aws-sdk/client-sqs';

const sqs = new SQSClient({});

export const handler = async (event) => {
  await sqs.send(new SendMessageCommand({
    QueueUrl: process.env.QUEUE_URL,
    MessageBody: JSON.stringify({
      userId: event.userId,
      action: 'send-email',
    }),
  }));

  return { statusCode: 202 };
};

// Consumer
export const handler = async (event) => {
  for (const record of event.Records) {
    const message = JSON.parse(record.body);

    if (message.action === 'send-email') {
      await sendEmail(message.userId);
    }
  }
};

Upstash (Redis-based)

import { Queue } from '@upstash/qstash';

const queue = new Queue({
  token: process.env.QSTASH_TOKEN,
});

// Enqueue
await queue.publish({
  url: 'https://example.com/api/process',
  body: JSON.stringify({ userId: '123' }),
});

// Process
export const handler = async (event) => {
  const { userId } = JSON.parse(event.body);
  await processUser(userId);
};

File Storage

S3

import { S3Client, PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

const s3 = new S3Client({});

// Upload
export const upload = async (event) => {
  const { filename, content } = JSON.parse(event.body);

  await s3.send(new PutObjectCommand({
    Bucket: process.env.BUCKET_NAME,
    Key: filename,
    Body: content,
  }));

  return { statusCode: 200 };
};

// Generate signed URL
export const getDownloadUrl = async (event) => {
  const { filename } = event.queryStringParameters;

  const command = new GetObjectCommand({
    Bucket: process.env.BUCKET_NAME,
    Key: filename,
  });

  const url = await getSignedUrl(s3, command, { expiresIn: 3600 });

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

Cron Jobs

EventBridge Schedules

# serverless.yml
functions:
  dailyReport:
    handler: handler.generateReport
    events:
      - schedule: cron(0 9 * * ? *)  # 9 AM UTC daily
export const generateReport = async () => {
  const data = await fetchDailyData();
  const report = generateReport(data);
  await emailReport(report);
};

Performance Optimization

1. Cold Start Mitigation

// Keep connections warm
let dbConnection;

export const handler = async (event) => {
  // Reuse connection if exists
  if (!dbConnection) {
    dbConnection = await createConnection();
  }

  const result = await dbConnection.query(event.query);
  return result;
};

2. Provisioned Concurrency

# serverless.yml
functions:
  api:
    handler: handler.handler
    provisionedConcurrency: 5  # Keep 5 instances warm

3. Connection Pooling

import { createPool } from 'mysql2/promise';

const pool = createPool({
  host: process.env.DB_HOST,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME,
  connectionLimit: 1,  # Serverless best practice
});

export const handler = async (event) => {
  const [rows] = await pool.query('SELECT * FROM users');
  return rows;
};

4. Caching

import { Redis } from '@upstash/redis';

const redis = new Redis({
  url: process.env.UPSTASH_URL,
  token: process.env.UPSTASH_TOKEN,
});

export const handler = async (event) => {
  const cacheKey = `user:${event.userId}`;

  // Check cache
  let user = await redis.get(cacheKey);

  if (!user) {
    // Fetch from database
    user = await db.getUser(event.userId);

    // Cache for 1 hour
    await redis.setex(cacheKey, 3600, JSON.stringify(user));
  }

  return user;
};

Cost Optimization

1. Right-Size Memory

# More memory = faster execution = potentially cheaper
functions:
  api:
    handler: handler.handler
    memorySize: 1024  # Test different sizes

2. Batch Processing

// Process multiple items per invocation
export const handler = async (event) => {
  const items = event.Records; // Up to 10,000 from SQS

  await Promise.all(
    items.map(item => processItem(item))
  );
};

3. Set Timeouts

functions:
  quickApi:
    handler: handler.handler
    timeout: 3  # Don't use default 6 seconds if not needed

Monitoring and Debugging

CloudWatch Logs

export const handler = async (event) => {
  console.log('Event received:', JSON.stringify(event));

  try {
    const result = await processEvent(event);
    console.log('Success:', result);
    return result;
  } catch (error) {
    console.error('Error:', error);
    throw error;
  }
};

X-Ray Tracing

import AWSXRay from 'aws-xray-sdk-core';
import AWS from 'aws-sdk';

const dynamodb = AWSXRay.captureAWSClient(new AWS.DynamoDB.DocumentClient());

export const handler = async (event) => {
  // Traced automatically
  const result = await dynamodb.get({
    TableName: 'Users',
    Key: { id: event.userId },
  });

  return result.Item;
};

When to Use Serverless

Great for: ✅ Variable/unpredictable traffic ✅ Event-driven workloads ✅ Rapid prototyping ✅ Microservices ✅ Background jobs ✅ API backends

Consider alternatives for: ❌ Long-running processes (> 15 minutes) ❌ Consistent high traffic (cheaper to use servers) ❌ Stateful applications ❌ Real-time/WebSocket intensive ❌ Complex deployments

Conclusion

Serverless architecture offers incredible benefits:

  • No server management
  • Automatic scaling
  • Pay-per-use pricing
  • Faster time to market

But it requires new thinking:

  • Stateless design
  • Event-driven patterns
  • Distributed systems knowledge
  • Cost awareness

Start small, learn the patterns, and scale as needed. Serverless isn't the answer to everything, but for the right workloads, it's transformative.

Ready to go serverless?

👨‍💻

Jordan Patel

Webentwickler & Technologie-Enthusiast