
使用NestJS和Prisma构建REST API:身份验证
Serverless 应用程序已经存在了许多年,但是在过去的两年里,它的受欢迎程度直线上升。在本文中,你将了解如何从头构建 RESTful API 并将其部署到 AWS(Amazon Web Services)上。
尽管名为“Serverless”,但它确实需要服务器来运行代码。关键的区别在于,你不需要管理运行代码的服务器,这消除了管理服务器、负载平衡器、应用补丁和扩展服务器的负担。
Serverless 应用程序可以在大多数云(AWS、Azure、GCP 和 IBM Cloud)上运行,但在本文中,我们将重点讨论 AWS,因为它是目前应用最广泛的云计算平台。
Serverless 应用程序主要有四个部分组成:
人们经常犯的一个错误是混淆了 Serverless 架构和框架的概念。Serverless 框架是一个开源 CLI 工具,它使代码部署变得更加容易且更可维护。它允许你将基础结构定义为代码(数据库、队列、文件存储、API 等),而不是手动登录通过 Web 接口创建它们。
框架与云无关,被广泛采用,有良好的学习文档,并有一个大型的社区来支持它。
使用 Serverless 框架开发 Serverless 应用程序有四个关键组件。
函数
函数是 AWS Lambda 函数,它是你编写业务逻辑的地方,它由事件调用。
常见函数举例:
事件
任何触发函数运行的操作都被认为是一个事件。
常见事件举例:
资源
资源是函数所依赖的 AWS 基础设施。
常见的资源:
服务
服务是框架的组织单元。你可以将它看作一个项目文件,尽管你可以为一个应用程序提供多个服务。它是定义函数、触发函数的事件和函数使用资源的地方,所有这些都在一个名为 serverless.yml 的文件中。
在本教程中,你将构建一个图书 API,该 API 将图书保存到一个 NoSQL 数据存储(DynamoDB)中,并将用于管理图书的 CRUD(创建、读取、更新和删除)。
整个项目Github地址: https://github.com/JamieLivingstone/serverless-node-rest-api
前提
1)首先,你需要安装全局 Serverless 框架。
npm install -g serverless
2)创建一个新目录“book-api”,并用你最喜欢的代码编辑器打开。
3)在项目根目录下运行如下命令生成新项目的框架。
serverless create --template aws-nodejs
4)在项目根目录下新建一个文件“package.json”,并将下面的内容粘贴到这个文件中。
{
"name": "book-app",
"version": "1.0.0",
"description": "Serverless book management API",
"dependencies": {
"@hapi/joi": "^15.0.3",
"aws-sdk": "^2.466.0",
"uuid": "^3.3.2"
}
}
5)在项目的根目录下运行如下命令安装项目依赖。
npm install
你的项目现在应该是下面这个样子:
book-api
- node_modules
- serverles.yml
- handler.js
- .gitignore
- .package.json
Serverless 框架简化了在代码中定义基础设施的过程,你可以在“serverless.yml”中配置应用程序基础参数。当你部署代码时,配置将转换为 AWS 提供的 CloudFormation 模板,它允许你在代码中创建和管理基础设施。
要构建 API,你需要以下基础设施:
打开项目根目录下的文件“serverless.yml”,并用下面的内容替换。
service: book-api
provider:
name: aws
runtime: nodejs10.x
stage: development
region: eu-west-1
environment:
BOOKS_TABLE: "books"
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:DescribeTable
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
Resource:
Fn::Join:
- ""
- - "arn:aws:dynamodb:*:*:table/"
- Ref: BooksTable
functions:
create:
handler: books/create.handler
events:
- http:
path: books
method: post
cors: true
update:
handler: books/update.handler
events:
- http:
path: books/{id}
method: put
cors: true
list:
handler: books/list.handler
events:
- http:
path: books
method: get
cors: true
get:
handler: books/get.handler
events:
- http:
path: books/{id}
method: get
cors: true
delete:
handler: books/delete.handler
events:
- http:
path: books/{id}
method: delete
cors: true
resources:
Resources:
BooksTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: ${self:provider.environment.BOOKS_TABLE}
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
这个文件乍一看可能有点令人生畏,但让我们花点时间来消化代码,进一步了解每个部分在做什么。
Service
你的服务的名称,最好将其命名为描述性的名称,因为它在 AWS 的日志和各种其他位置中使用。
Provider
Provider 块是指定希望部署到的云平台和特定于给定云提供者的配置的地方。
Functions
这是指定函数和调用函数的事件的地方。正如你在上面的配置中所看到的,它指定了五个供各种请求类型的特定端点的 HTTP 事件调用的函数。
让我们看看其中一个函数,并试着理解它是如何工作的。
functions:
create:
handler: books/create.handler
events:
- http:
path: books
method: post
cors: true
我们可以设想一下,我们的代码将被做处理如下:
Resources
这是指定应用程序所依赖的 AWS 基础设施的地方。正如你在配置中看到的,它告诉 AWS 新建一个名为“books”的 DynamoDB 表(通过自引用环境变量)。
在将数据插入数据库之前验证数据始终是一种很好的实践,为了处理这个问题,你将使用一个名为“Joi”的开源模式验证器。
1)在项目根目录下创建一个新目录“books”。
2)在 books 目录下创建一个文件“schema.js”,并将如下内容粘贴到这个文件中。
const Joi = require("@hapi/joi");
const bookSchema = Joi.object().keys({
title: Joi.string()
.min(1)
.required(),
author: Joi.string()
.min(1)
.required(),
pages: Joi.number().required()
});
function validateModel(model) {
return Joi.validate(model, bookSchema, { abortEarly: false });
};
module.exports = {
validateModel
};
如你所见,我们定义了图书模式及其属性,并输出了一个函数“validateModel”,你将使用它来验证 handler 函数中的请求。
现在是绑定 handler 函数的时候了,这些函数是在“serverless.yml”文件中指定的。你可能已经注意到,当你搭建项目时,它创建了一个名为“handler.js”的文件。我们不会使用这个,因为把所有的代码放在一个文件中是不好的做法,因为它变得非常复杂,打破了单一职责原则,你可以删除这个文件。 Create
在 books 目录下新建一个文件“create.js”,并将如下内容粘贴到这个文件中。
"use strict";
const AWS = require("aws-sdk");
const client = new AWS.DynamoDB.DocumentClient();
const uuid = require("uuid");
const { validateModel } = require("./schema");
module.exports.handler = async function createBook(event, context, callback) {
const timestamp = new Date().getTime();
const data = JSON.parse(event.body);
const validation = validateModel(data);
if (validation.error) {
const response = {
statusCode: 400,
body: JSON.stringify(validation.error.details)
};
return callback(null, response);
}
const params = {
TableName: process.env.BOOKS_TABLE,
Item: {
id: uuid.v1(),
created_at: timestamp,
updated_at: timestamp,
title: data.title,
author: data.author,
pages: data.pages
}
};
await client.put(params).promise();
const response = {
statusCode: 201,
body: JSON.stringify(params.Item)
};
return callback(null, response);
};
上面的函数负责将图书保存到数据库中并以新创建的图书作为响应。
它可以分为以下几个步骤:
Update
在 books 目录下新建一个文件“update.js”,并将如下内容粘贴到这个文件中。
"use strict";
const AWS = require("aws-sdk");
const client = new AWS.DynamoDB.DocumentClient();
const { validateModel } = require("./schema");
module.exports.handler = async function updateBook(event, context, callback) {
const timestamp = new Date().getTime();
const data = JSON.parse(event.body);
const validation = validateModel(data);
if (validation.error) {
const response = {
statusCode: 400,
body: JSON.stringify(validation.error.details)
};
return callback(null, response);
}
const params = {
TableName: process.env.BOOKS_TABLE,
Key: {
id: event.pathParameters.id
},
ExpressionAttributeValues: {
":updated_at": timestamp,
":title": data.title,
":author": data.author,
":pages": data.pages
},
UpdateExpression: "SET updated_at = :updated_at, title = :title, author = :author, pages = :pages",
ReturnValues: "ALL_NEW"
};
const result = await client.update(params).promise();
const response = {
statusCode: 200,
body: JSON.stringify(result.Attributes)
};
return callback(null, response);
};
List
在 books 目录下新建一个文件“list.js”,并将如下内容粘贴到这个文件中。
"use strict";
const AWS = require("aws-sdk");
const client = new AWS.DynamoDB.DocumentClient();
module.exports.handler = async function listBooks(event, context, callback) {
const params = {
TableName: process.env.BOOKS_TABLE
};
const { Items = [] } = await client.scan(params).promise();
callback(null, {
statusCode: 200,
body: JSON.stringify(Items)
});
};
Get
在 books 目录下新建一个文件“get.js”,并将如下内容粘贴到这个文件中。
"use strict";
const AWS = require("aws-sdk");
const client = new AWS.DynamoDB.DocumentClient();
module.exports.handler = async function getBook(event, context, callback) {
const params = {
TableName: process.env.BOOKS_TABLE,
Key: {
id: event.pathParameters.id
}
};
const { Item } = await client.get(params).promise();
const response = {
statusCode: Item ? 200 : 404,
body: JSON.stringify(Item ? Item : { message: "Book not found!" })
};
callback(null, response);
};
Delete
在 books 目录下新建一个文件“delete.js”,并将如下内容粘贴到这个文件中。
"use strict";
const AWS = require("aws-sdk");
const client = new AWS.DynamoDB.DocumentClient();
module.exports.handler = async function deleteBook(event, context, callback) {
const params = {
TableName: process.env.BOOKS_TABLE,
Key: {
id: event.pathParameters.id
}
};
await client.delete(params).promise();
const response = {
statusCode: 200
};
return callback(null, response);
};
使用 Serverless 框架部署应用程序非常简单!这就是将基础设施作为代码的好处所在。
serverless deplo
恭喜!你已经完成 Serverless 应用程序的部署!
英文原文: https://jamielivingstone.dev/build-a-rest-api-with-the-serverless-framework-and-deploy-to-aws/
文章转自微信公众号@前端之巅