Serverless 架构实战:用 AWS Lambda + Vercel 部署全栈应用
·
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
更多推荐
所有评论(0)