Serverless 架构实战:用 AWS Lambda + Vercel 部署全栈应用

嗨,我是欧阳瑞。今天分享一下我最近在做的 Serverless 架构实践。作为追求极致自动化的极客,Serverless 真的让我解放了双手——再也不用管服务器运维了!

什么是 Serverless?

简单说就是「用多少,花多少」,而且完全不用管服务器。代码部署上去,云厂商自动帮你扩缩容。

项目架构

我们要做一个极简的 Web3 项目画廊,架构如下:

  • 前端:Next.js,部署在 Vercel
  • 后端:AWS Lambda(API Gateway)
  • 数据库:DynamoDB(也是 Serverless 的)

后端:AWS Lambda

先搞后端。我们用 Serverless Framework 来管理:

npm install -g serverless
mkdir serverless-api && cd serverless-api
serverless create --template aws-nodejs --name web3-gallery-api
npm install aws-sdk uuid

配置 serverless.yml

service: web3-gallery-api

provider:
  name: aws
  runtime: nodejs18.x
  region: us-east-1
  environment:
    DYNAMODB_TABLE: ${self:service}-${sls:stage}
  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:Query
        - dynamodb:Scan
        - dynamodb:GetItem
        - dynamodb:PutItem
      Resource: "arn:aws:dynamodb:${aws:region}:*:table/${self:provider.environment.DYNAMODB_TABLE}"

functions:
  listProjects:
    handler: handler.listProjects
    events:
      - http:
          path: projects
          method: get
          cors: true
  createProject:
    handler: handler.createProject
    events:
      - http:
          path: projects
          method: post
          cors: true

resources:
  Resources:
    Web3GalleryTable:
      Type: AWS::DynamoDB::Table
      Properties:
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: S
        KeySchema:
          - AttributeName: id
            KeyType: HASH
        BillingMode: PAY_PER_REQUEST
        TableName: ${self:provider.environment.DYNAMODB_TABLE}

编写 handler.js

const AWS = require('aws-sdk');
const { v4: uuidv4 } = require('uuid');

const dynamoDb = new AWS.DynamoDB.DocumentClient();
const TABLE_NAME = process.env.DYNAMODB_TABLE;

module.exports.listProjects = async () => {
  const params = { TableName: TABLE_NAME };
  
  try {
    const result = await dynamoDb.scan(params).promise();
    return {
      statusCode: 200,
      headers: {
        'Access-Control-Allow-Origin': '*',
      },
      body: JSON.stringify(result.Items),
    };
  } catch (error) {
    return {
      statusCode: 500,
      body: JSON.stringify({ error: 'Could not fetch projects' }),
    };
  }
};

module.exports.createProject = async (event) => {
  const data = JSON.parse(event.body);
  const params = {
    TableName: TABLE_NAME,
    Item: {
      id: uuidv4(),
      title: data.title,
      description: data.description,
      techStack: data.techStack || [],
      isWeb3: data.isWeb3 || false,
      createdAt: Date.now(),
    },
  };

  try {
    await dynamoDb.put(params).promise();
    return {
      statusCode: 201,
      headers: {
        'Access-Control-Allow-Origin': '*',
      },
      body: JSON.stringify(params.Item),
    };
  } catch (error) {
    return {
      statusCode: 500,
      body: JSON.stringify({ error: 'Could not create project' }),
    };
  }
};

部署:

serverless deploy

记下输出的 API URL,后面要用。

前端:Next.js + Vercel

npx create-next-app@latest web3-gallery-frontend --typescript --tailwind --app
cd web3-gallery-frontend

app/page.tsx

'use client';

import { useEffect, useState } from 'react';

interface Project {
  id: string;
  title: string;
  description: string;
  techStack: string[];
  isWeb3: boolean;
}

const API_URL = process.env.NEXT_PUBLIC_API_URL || '';

export default function Home() {
  const [projects, setProjects] = useState<Project[]>([]);
  const [title, setTitle] = useState('');
  const [description, setDescription] = useState('');

  useEffect(() => {
    fetchProjects();
  }, []);

  const fetchProjects = async () => {
    const res = await fetch(`${API_URL}/projects`);
    const data = await res.json();
    setProjects(data);
  };

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    await fetch(`${API_URL}/projects`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        title,
        description,
        isWeb3: true,
        techStack: ['Next.js', 'Lambda', 'DynamoDB'],
      }),
    });
    setTitle('');
    setDescription('');
    fetchProjects();
  };

  return (
    <div className="min-h-screen bg-gradient-to-br from-gray-900 to-black text-cyan-400 p-8">
      <h1 className="text-4xl font-bold mb-8 text-magenta-500">
        Web3 项目画廊
      </h1>

      <form onSubmit={handleSubmit} className="mb-8 space-y-4">
        <input
          type="text"
          placeholder="项目名称"
          value={title}
          onChange={(e) => setTitle(e.target.value)}
          className="w-full p-2 bg-gray-800 border border-cyan-400 rounded"
        />
        <textarea
          placeholder="项目描述"
          value={description}
          onChange={(e) => setDescription(e.target.value)}
          className="w-full p-2 bg-gray-800 border border-cyan-400 rounded"
        />
        <button
          type="submit"
          className="px-4 py-2 bg-magenta-500 text-white rounded hover:bg-magenta-600"
        >
          添加项目
        </button>
      </form>

      <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
        {projects.map((project) => (
          <div
            key={project.id}
            className="border border-cyan-400 p-4 rounded bg-gray-800/50"
          >
            <h2 className="text-xl font-bold text-cyan-300">{project.title}</h2>
            <p className="text-gray-300 mt-2">{project.description}</p>
            <div className="mt-3 flex flex-wrap gap-2">
              {project.techStack.map((tech, i) => (
                <span
                  key={i}
                  className="px-2 py-1 bg-cyan-500/20 text-xs rounded"
                >
                  {tech}
                </span>
              ))}
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

别忘了在项目根目录建 .env.local

NEXT_PUBLIC_API_URL=https://your-api-gateway-url.amazonaws.com/dev

部署前端

推到 GitHub,然后在 Vercel 导入仓库,一键部署。别忘了在 Vercel 后台设置环境变量。

成本估算

假设每天 1000 次请求:

  • Lambda:几乎免费(免费额度每月 100 万次)
  • DynamoDB:几乎免费(免费额度 25GB)
  • Vercel:个人项目免费
  • API Gateway:$0.35/月

四舍五入等于不要钱!

总结

Serverless 架构真的适合个人项目和快速迭代的创业项目。我的鬃狮蜥 Hash 现在更开心了,因为我再也不用半夜起来修服务器了,有更多时间陪它。

有问题欢迎留言,我是欧阳瑞,极客之路,永无止境!


技术栈:Serverless · AWS Lambda · DynamoDB · Vercel · Next.js

更多推荐