Voltar para o Blog
Anúncio

Serverless em 2025: Por Que Node.js Domina (E Como Usar)

Olá HaWkers, lembra quando "serverless" parecia apenas um hype passageiro? Em 2025, serverless não é mais tendência - é mainstream. E há uma razão clara para Node.js ter se tornado a linguagem dominante neste ecossistema: cold start rápido, runtime leve, e ecossistema massivo.

Empresas como Netflix processam bilhões de requests serverless por dia. A Coca-Cola reduziu custos de infraestrutura em 65% migrando para serverless. E desenvolvedores individuais criam aplicações que escalam automaticamente de 0 a milhões de usuários - sem tocar em um único servidor.

Mas como você realmente constrói aplicações serverless robustas com Node.js? Vamos explorar desde o básico até padrões avançados usados em produção.

O Que É Serverless e Por Que Usar Node.js

Serverless não significa "sem servidores" - significa que você não gerencia servidores. Você escreve funções, faz deploy, e o provider cuida de tudo: scaling, disponibilidade, segurança, patches.

Node.js domina serverless por razões técnicas sólidas:

  1. Cold start rápido: ~100-200ms vs segundos em Python/Java
  2. Runtime leve: Menos memória = custos menores
  3. Event-driven nativo: Perfeito para arquitetura serverless
  4. npm ecosystem: Milhões de packages prontos
  5. JavaScript universal: Mesmo código no front e backend
Anúncio

Sua Primeira Função Serverless com AWS Lambda

Vamos começar com o básico - uma função Lambda simples:

// handler.js - Função Lambda básica
export const handler = async (event) => {
  console.log('Event received:', JSON.stringify(event, null, 2));

  // Parse do body se vier de API Gateway
  const body = event.body ? JSON.parse(event.body) : event;

  // Lógica da função
  const response = {
    message: 'Hello from Lambda!',
    input: body,
    timestamp: new Date().toISOString()
  };

  // Retornar resposta para API Gateway
  return {
    statusCode: 200,
    headers: {
      'Content-Type': 'application/json',
      'Access-Control-Allow-Origin': '*'
    },
    body: JSON.stringify(response)
  };
};

// Para testar localmente
if (process.env.LOCAL_TEST) {
  const testEvent = {
    body: JSON.stringify({ name: 'Jeff', action: 'test' })
  };

  handler(testEvent).then(result => {
    console.log('Result:', result);
  });
}

Deploy com Serverless Framework

O Serverless Framework simplifica drasticamente o processo de deploy:

# serverless.yml
service: my-api

provider:
  name: aws
  runtime: nodejs20.x
  region: us-east-1
  memorySize: 256
  timeout: 10
  environment:
    STAGE: ${opt:stage, 'dev'}
    DB_CONNECTION: ${env:DB_CONNECTION}

functions:
  hello:
    handler: handler.handler
    events:
      - http:
          path: /hello
          method: POST
          cors: true

  processUser:
    handler: users.process
    events:
      - http:
          path: /users/{id}
          method: GET
          cors: true
      - sns:
          topicName: user-updates
          displayName: User Updates Topic

  scheduledTask:
    handler: tasks.scheduled
    events:
      - schedule:
          rate: rate(5 minutes)
          enabled: true

plugins:
  - serverless-offline
  - serverless-webpack

custom:
  webpack:
    webpackConfig: './webpack.config.js'
    includeModules: true
// users.js - Função mais complexa
import AWS from 'aws-sdk';

const dynamodb = new AWS.DynamoDB.DocumentClient();
const TABLE_NAME = process.env.USERS_TABLE || 'users';

export const process = async (event) => {
  const userId = event.pathParameters?.id;

  if (!userId) {
    return {
      statusCode: 400,
      body: JSON.stringify({ error: 'User ID is required' })
    };
  }

  try {
    // Buscar usuário do DynamoDB
    const result = await dynamodb.get({
      TableName: TABLE_NAME,
      Key: { userId }
    }).promise();

    if (!result.Item) {
      return {
        statusCode: 404,
        body: JSON.stringify({ error: 'User not found' })
      };
    }

    // Processar dados do usuário
    const processedUser = {
      ...result.Item,
      lastAccessed: new Date().toISOString(),
      processedBy: 'lambda'
    };

    // Atualizar timestamp de acesso
    await dynamodb.update({
      TableName: TABLE_NAME,
      Key: { userId },
      UpdateExpression: 'SET lastAccessed = :timestamp',
      ExpressionAttributeValues: {
        ':timestamp': processedUser.lastAccessed
      }
    }).promise();

    return {
      statusCode: 200,
      headers: {
        'Content-Type': 'application/json',
        'Cache-Control': 'max-age=300'
      },
      body: JSON.stringify(processedUser)
    };

  } catch (error) {
    console.error('Error processing user:', error);

    return {
      statusCode: 500,
      body: JSON.stringify({
        error: 'Internal server error',
        message: error.message
      })
    };
  }
};

Serverless deployment

Anúncio

Padrões Avançados: Arquitetura Serverless em Produção

Aplicações serverless reais precisam de padrões robustos. Vamos explorar alguns:

1. Connection Pooling para Bancos de Dados

Um erro comum: criar nova conexão de DB em cada invocação. A solução? Connection pooling global:

// db.js - Connection pooling otimizado
import mysql from 'mysql2/promise';

let pool = null;

export const getPool = () => {
  if (!pool) {
    pool = mysql.createPool({
      host: process.env.DB_HOST,
      user: process.env.DB_USER,
      password: process.env.DB_PASSWORD,
      database: process.env.DB_NAME,
      waitForConnections: true,
      connectionLimit: 2, // Lambda: poucas conexões!
      queueLimit: 0,
      enableKeepAlive: true,
      keepAliveInitialDelay: 0
    });

    console.log('Database pool created');
  }

  return pool;
};

// Lambda handler com pool reusável
export const handler = async (event) => {
  const pool = getPool(); // Reutiliza conexão entre invocações!

  try {
    const [rows] = await pool.execute(
      'SELECT * FROM users WHERE id = ?',
      [event.userId]
    );

    return {
      statusCode: 200,
      body: JSON.stringify(rows[0])
    };

  } catch (error) {
    console.error('Database error:', error);
    throw error;
  }

  // NÃO fechar o pool - reutilizar na próxima invocação!
};

2. Middleware Pattern para Funções Lambda

Middleware torna funções mais limpas e reutilizáveis:

// middleware.js - Sistema de middleware para Lambda
export class LambdaMiddleware {
  constructor(handler) {
    this.handler = handler;
    this.middlewares = [];
  }

  use(middleware) {
    this.middlewares.push(middleware);
    return this;
  }

  async execute(event, context) {
    let index = 0;

    const next = async () => {
      if (index < this.middlewares.length) {
        const middleware = this.middlewares[index++];
        await middleware(event, context, next);
      } else {
        return this.handler(event, context);
      }
    };

    try {
      return await next();
    } catch (error) {
      console.error('Middleware error:', error);
      throw error;
    }
  }
}

// Middlewares úteis
export const jsonBodyParser = async (event, context, next) => {
  if (event.body && typeof event.body === 'string') {
    try {
      event.body = JSON.parse(event.body);
    } catch (error) {
      return {
        statusCode: 400,
        body: JSON.stringify({ error: 'Invalid JSON body' })
      };
    }
  }
  return next();
};

export const cors = (origins = '*') => {
  return async (event, context, next) => {
    const result = await next();

    return {
      ...result,
      headers: {
        ...result.headers,
        'Access-Control-Allow-Origin': origins,
        'Access-Control-Allow-Credentials': true,
        'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE,OPTIONS',
        'Access-Control-Allow-Headers': 'Content-Type,Authorization'
      }
    };
  };
};

export const errorHandler = async (event, context, next) => {
  try {
    return await next();
  } catch (error) {
    console.error('Unhandled error:', error);

    return {
      statusCode: error.statusCode || 500,
      body: JSON.stringify({
        error: error.message || 'Internal server error',
        requestId: context.requestId
      })
    };
  }
};

export const logging = async (event, context, next) => {
  const start = Date.now();

  console.log('Request started:', {
    requestId: context.requestId,
    path: event.path,
    method: event.httpMethod
  });

  const result = await next();

  const duration = Date.now() - start;
  console.log('Request completed:', {
    requestId: context.requestId,
    duration: `${duration}ms`,
    statusCode: result.statusCode
  });

  return result;
};

// Uso dos middlewares
import { LambdaMiddleware, jsonBodyParser, cors, errorHandler, logging } from './middleware.js';

const mainHandler = async (event, context) => {
  // Lógica principal da função
  const { name, email } = event.body;

  if (!name || !email) {
    const error = new Error('Name and email are required');
    error.statusCode = 400;
    throw error;
  }

  // Processar dados
  return {
    statusCode: 200,
    body: JSON.stringify({
      message: 'User created',
      user: { name, email }
    })
  };
};

// Compor handler com middlewares
const middleware = new LambdaMiddleware(mainHandler);

middleware
  .use(logging)
  .use(jsonBodyParser)
  .use(cors('https://myapp.com'))
  .use(errorHandler);

export const handler = (event, context) => middleware.execute(event, context);

3. Step Functions para Workflows Complexos

Para processos com múltiplos passos, Step Functions é essencial:

// workflow-functions.js - Funções para Step Functions
import AWS from 'aws-sdk';

const s3 = new AWS.S3();
const sns = new AWS.SNS();

// Passo 1: Validar input
export const validateInput = async (event) => {
  const { userId, fileKey } = event;

  if (!userId || !fileKey) {
    throw new Error('userId and fileKey are required');
  }

  // Verificar se arquivo existe
  try {
    await s3.headObject({
      Bucket: process.env.BUCKET_NAME,
      Key: fileKey
    }).promise();

    return {
      ...event,
      validated: true,
      timestamp: new Date().toISOString()
    };

  } catch (error) {
    if (error.code === 'NotFound') {
      throw new Error(`File not found: ${fileKey}`);
    }
    throw error;
  }
};

// Passo 2: Processar arquivo
export const processFile = async (event) => {
  const { fileKey } = event;

  // Baixar arquivo do S3
  const file = await s3.getObject({
    Bucket: process.env.BUCKET_NAME,
    Key: fileKey
  }).promise();

  const content = file.Body.toString('utf-8');
  const lines = content.split('\n');

  // Processar cada linha
  const processed = lines.map(line => {
    // Lógica de processamento
    return line.trim().toUpperCase();
  });

  // Salvar resultado processado
  const resultKey = `processed/${fileKey}`;
  await s3.putObject({
    Bucket: process.env.BUCKET_NAME,
    Key: resultKey,
    Body: processed.join('\n'),
    ContentType: 'text/plain'
  }).promise();

  return {
    ...event,
    processed: true,
    resultKey,
    lineCount: lines.length
  };
};

// Passo 3: Notificar usuário
export const notifyUser = async (event) => {
  const { userId, resultKey, lineCount } = event;

  const message = `
    File processing completed!

    User: ${userId}
    Result: ${resultKey}
    Lines processed: ${lineCount}
  `;

  await sns.publish({
    TopicArn: process.env.SNS_TOPIC_ARN,
    Subject: 'File Processing Complete',
    Message: message
  }).promise();

  return {
    ...event,
    notified: true,
    completedAt: new Date().toISOString()
  };
};

// Passo 4: Cleanup (erro ou sucesso)
export const cleanup = async (event) => {
  const { fileKey } = event;

  // Deletar arquivo original
  await s3.deleteObject({
    Bucket: process.env.BUCKET_NAME,
    Key: fileKey
  }).promise();

  return {
    ...event,
    cleaned: true
  };
};
// state-machine.json - Definição do Step Functions
{
  "Comment": "File processing workflow",
  "StartAt": "ValidateInput",
  "States": {
    "ValidateInput": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:us-east-1:123456789012:function:validateInput",
      "Next": "ProcessFile",
      "Catch": [{
        "ErrorEquals": ["States.ALL"],
        "Next": "NotifyError"
      }]
    },
    "ProcessFile": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:us-east-1:123456789012:function:processFile",
      "Next": "NotifyUser",
      "Catch": [{
        "ErrorEquals": ["States.ALL"],
        "Next": "NotifyError"
      }]
    },
    "NotifyUser": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:us-east-1:123456789012:function:notifyUser",
      "Next": "Cleanup"
    },
    "Cleanup": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:us-east-1:123456789012:function:cleanup",
      "End": true
    },
    "NotifyError": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:us-east-1:123456789012:function:notifyError",
      "Next": "Cleanup"
    }
  }
}
Anúncio

Providers Modernos: Além da AWS

Em 2025, há alternativas excelentes à AWS Lambda:

Cloudflare Workers

// Cloudflare Worker - Edge computing ultra-rápido
export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);

    // Roteamento
    if (url.pathname === '/api/users') {
      return handleUsers(request, env);
    }

    if (url.pathname.startsWith('/api/data')) {
      return handleData(request, env);
    }

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

async function handleUsers(request, env) {
  // Cache no Cloudflare KV
  const cached = await env.KV_STORE.get('users', 'json');

  if (cached) {
    return new Response(JSON.stringify(cached), {
      headers: {
        'Content-Type': 'application/json',
        'Cache-Control': 'max-age=60'
      }
    });
  }

  // Buscar de origem
  const users = await fetchUsersFromOrigin();

  // Cachear por 5 minutos
  await env.KV_STORE.put('users', JSON.stringify(users), {
    expirationTtl: 300
  });

  return new Response(JSON.stringify(users), {
    headers: {
      'Content-Type': 'application/json'
    }
  });
}

async function fetchUsersFromOrigin() {
  // Implementação real
  return [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' }
  ];
}

Vercel Functions

// api/hello.js - Vercel Serverless Function
export default async function handler(request, response) {
  const { method, body } = request;

  if (method !== 'POST') {
    return response.status(405).json({ error: 'Method not allowed' });
  }

  try {
    const data = typeof body === 'string' ? JSON.parse(body) : body;

    // Processar requisição
    const result = {
      message: 'Success',
      processed: data,
      timestamp: new Date().toISOString()
    };

    return response.status(200).json(result);

  } catch (error) {
    return response.status(500).json({ error: error.message });
  }
}

// Edge config (opcional)
export const config = {
  runtime: 'edge', // Ou 'nodejs' para Node.js runtime
};
Anúncio

Performance e Custos: Otimização Crítica

Serverless pode ficar caro se mal otimizado. Estratégias essenciais:

1. Minimizar Cold Starts

// Pré-carregar dependências no escopo global
import AWS from 'aws-sdk';
import database from './db.js';

const dynamodb = new AWS.DynamoDB.DocumentClient();
const pool = database.getPool();

// Warm-up de conexões
if (process.env.WARMUP) {
  console.log('Warming up...');
  pool.execute('SELECT 1').catch(console.error);
}

export const handler = async (event) => {
  // Conexões já prontas - zero overhead!
  // Handler code...
};

2. Monitoramento e Alertas

// monitoring.js - Monitoramento integrado
import AWS from 'aws-sdk';

const cloudwatch = new AWS.CloudWatch();

export class MetricsCollector {
  constructor(namespace = 'MyApp') {
    this.namespace = namespace;
    this.metrics = [];
  }

  record(name, value, unit = 'None') {
    this.metrics.push({
      MetricName: name,
      Value: value,
      Unit: unit,
      Timestamp: new Date()
    });
  }

  async flush() {
    if (this.metrics.length === 0) return;

    await cloudwatch.putMetricData({
      Namespace: this.namespace,
      MetricData: this.metrics
    }).promise();

    this.metrics = [];
  }
}

// Uso no handler
export const handler = async (event, context) => {
  const metrics = new MetricsCollector();
  const start = Date.now();

  try {
    // Lógica da função
    const result = await processEvent(event);

    // Registrar sucesso
    metrics.record('FunctionSuccess', 1, 'Count');
    metrics.record('ProcessingTime', Date.now() - start, 'Milliseconds');

    await metrics.flush();

    return result;

  } catch (error) {
    // Registrar erro
    metrics.record('FunctionError', 1, 'Count');
    await metrics.flush();

    throw error;
  }
};

O Futuro do Serverless e Node.js

Em 2025, serverless está maduro. As próximas evoluções incluem:

  • WebAssembly no edge: Performance nativa em Cloudflare/Vercel
  • Serverless containers: AWS Fargate e Cloud Run
  • AI-optimized functions: GPUs serverless para ML
  • Multi-cloud orchestration: Terraform Cloud e Pulumi
  • Observability nativa: OpenTelemetry integrado

Node.js continuará dominando porque o JavaScript é ubíquo e o ecossistema npm é imbatível. Como desenvolvedor, dominar serverless com Node.js te coloca em uma posição privilegiada para construir a próxima geração de aplicações escaláveis.

Se você quer explorar mais sobre arquiteturas modernas, recomendo ler meu artigo sobre Microservices com Node.js: Arquitetura Moderna em 2025 onde discuto padrões complementares ao serverless.

Bora pra cima! 🦅

📚 Quer Aprofundar Seus Conhecimentos em JavaScript?

Este artigo cobriu serverless com Node.js, mas há muito mais para explorar no mundo do desenvolvimento moderno.

Desenvolvedores que investem em conhecimento sólido e estruturado tendem a ter mais oportunidades no mercado.

Material de Estudo Completo

Se você quer dominar JavaScript do básico ao avançado, preparei um guia completo:

Opções de investimento:

  • 3x de R$34,54 no cartão
  • ou R$97,90 à vista

👉 Conhecer o Guia JavaScript

💡 Material atualizado com as melhores práticas do mercado

Anúncio
Post anteriorPróximo post

Comentários (0)

Esse artigo ainda não possui comentários 😢. Seja o primeiro! 🚀🦅

Adicionar comentário