使用 NodeJS 通过 Google Cloud Functions 创建无服务器 REST API 的初学者指南
使用 Google Cloud Functions(无服务器)的 API REST
使用 Google Cloud Functions(无服务器)的 API REST
无服务器应用程序的重要性日益凸显。它允许你专注于应用程序代码/测试,而无需担心配置、部署流程或可扩展性。
我们将创建一个通过 REST URL 公开的函数。每次收到 HTTP(S) 请求时,该函数都会被调用。
在执行期间,将调用一个快速服务器来公开我们的 REST 服务。
我们要建造什么?
- Firestore 数据库上的 CRUD 服务(创建、读取、更新和删除)的 Express API。
- 使用 Google Cloud Function 公开我们的 Express 服务器
- 使用 Cloud CLI 部署我们的 Google Cloud Function。
创建我们的 Firebase 项目
为了创建我们的第一个项目,让我们在这里进行操作。选择“添加项目”,项目名称必须是唯一的,我们使用前缀github-ring-{github_user}
,在本例中为 github-ring-levinm。请务必选择 Firestore 作为我们的数据库。
要创建我们的数据库,请单击“开发” > “数据库”,然后选择“以测试模式启动”。
在本地初始化我们的项目
我们需要使用 NPM 安装 firebase。
npm install -g firebase-tools
然后,让我们登录我们的firebase帐户。
firebase login
........... input credentials
初始化项目
firebase init
........ select project
它将提示一个交互式控制台。
- 选择功能和托管选项。
- 你想用什么语言来编写 Cloud Functions?TypeScript
- 你想使用 TSLint 来捕获可能的错误并强制执行代码风格吗?是的
- 您是否现在要使用 npm 安装依赖项?是的
- 您想使用哪个目录作为您的公共目录?按 Enter 键选择“公共”(这是默认选项)
- 配置为单页应用(将所有 URL 重写为 /index.html)?否
我们准备好了,我们的firebase项目已初始化。
安装 Express.js 和依赖项
cd functions
npm install --save express body-parser
创建我们的 Google Cloud 函数
打开src/index.ts
,它将成为我们的 Express.js 服务器的入口点
导入主库
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import * as express from 'express';
import * as bodyParser from "body-parser";
初始化 Firebase 以访问其服务
admin.initializeApp(functions.config().firebase);
初始化 Express.js 服务器
const app = express();
const main = express();
配置服务器。
- 让我们添加用于接收请求的路径。
- 选择 JSON 作为处理请求主体的主要解析器。
main.use('/api/v1', app);
main.use(bodyParser.json());
导出我们的函数。
最后,同样重要的是,让我们定义 Google Cloud Function 的名称,我们将使用 来暴露它export
。我们的函数将接收一个 Express 服务器对象(本例中为main
),该对象将用于处理请求。如果您想了解更多关于其工作原理的信息,可以查看Stackoverflow 上的这个好答案。
export const webApi = functions.https.onRequest(main);
创建我们的第一个服务
让我们公开一个仅返回字符串的 GET 端点。
app.get('/warm', (req, res) => {
res.send('Calentando para la pelea');
})
我们的src/index.ts
文件应该是这样的:
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import * as express from 'express';
import * as bodyParser from "body-parser";
admin.initializeApp(functions.config().firebase);
const app = express();
const main = express();
main.use('/api/v1', app);
main.use(bodyParser.json());
export const webApi = functions.https.onRequest(main);
app.get('/warmup', (request, response) => {
response.send('Warming up friend.');
})
部署我们的功能。
在部署之前,我们需要更改配置文件firebase.json
如下:
{ “功能”:{ "预部署":[ “npm --prefix \”$RESOURCE_DIR\” 运行 lint”, “npm --prefix \”$RESOURCE_DIR\” 运行构建” ] }, “托管”:{ "公共":"公共", “忽略”: [ “firebase.json”, "**/.*", “**/node_modules/**” ], “重写”:[ { "source": "/api/v1/**", “功能”:“webApi” } ] } }
该规则允许“路由”所有通过函数(我们导出的函数)api/v1
发送的请求。webApi
此外,Google CLI 默认安装 Typescript v2。因此,我们需要更新 Typescript 版本>=3.3.1
。您可以在 中进行操作functions.package.json
。
“devDependencies”:{ “tslint”:“~5.8.0”, “typescript”:“~3.3.1” },
重新安装依赖项。
cd functions
npm install
我们已做好部署准备。
firebase deploy
.....
✔ Deploy complete!
Project Console: https://console.firebase.google.com/project/github-ring-levivm/overview
Hosting URL: https://github-ring-levivm.firebaseapp.com
如果一切正常,Hosting URL 将是我们的 Google Cloud Function 端点。
测试我们的功能
让我们GET
使用CURL
$ curl -G "https://github-ring-levivm.firebaseapp.com/api/v1/warmup"
Warming up friend.
Rest API CRUD
让我们添加 CRUD 端点。我们将管理fights
信息。
创建记录
首先,让我们初始化数据库。我们打开我们的src/index.ts
,并在管理员初始化后添加这个
admin.initializeApp(functions.config().firebase);
const db = admin.firestore(); // Add this
为了创建战斗记录,让我们创建POST /fights/
端点。我们的战斗记录将包含winner
、loser
和title
。
app.post('/fights', async (request, response) => {
try {
const { winner, loser, title } = request.body;
const data = {
winner,
loser,
title
}
const fightRef = await db.collection('fights').add(data);
const fight = await fightRef.get();
response.json({
id: fightRef.id,
data: fight.data()
});
} catch(error){
response.status(500).send(error);
}
});
- 我们使用以下方式获取帖子数据
request.body
- 我们使用
add()
方法添加新的战斗,如果集合不存在(我们的情况),它将自动创建它。 - 为了获得实际的记录数据,我们必须使用
get()
参考。 - 使用 返回 json
response.json
。
获取记录
我们创建一个GET /fights/:id
端点以便通过 ID 获取战斗。
app.get('/fights/:id', async (request, response) => {
try {
const fightId = request.params.id;
if (!fightId) throw new Error('Fight ID is required');
const fight = await db.collection('fights').doc(fightId).get();
if (!fight.exists){
throw new Error('Fight doesnt exist.')
}
response.json({
id: fight.id,
data: fight.data()
});
} catch(error){
response.status(500).send(error);
}
});
- 我们使用 获取战斗 ID
request.params
。 - 我们验证 ID 是否不为空。
- 我们得到战斗并检查它是否存在。
- 如果战斗不存在,我们会抛出一个错误
- 如果存在争斗,我们将返回数据。
获取记录列表
我们创建一个GET /fights/
端点。
app.get('/fights', async (request, response) => {
try {
const fightQuerySnapshot = await db.collection('fights').get();
const fights = [];
fightQuerySnapshot.forEach(
(doc) => {
fights.push({
id: doc.id,
data: doc.data()
});
}
);
response.json(fights);
} catch(error){
response.status(500).send(error);
}
});
- 我们得到了一个集合快照。
- 我们遍历每个文档并将数据推送到数组中。
- 我们归还了我们的战斗名单。
更新记录
我们必须创建一个PUT /fights/:id
端点才能通过 来更新战斗id
。
app.put('/fights/:id', async (request, response) => {
try {
const fightId = request.params.id;
const title = request.body.title;
if (!fightId) throw new Error('id is blank');
if (!title) throw new Error('Title is required');
const data = {
title
};
const fightRef = await db.collection('fights')
.doc(fightId)
.set(data, { merge: true });
response.json({
id: fightId,
data
})
} catch(error){
response.status(500).send(error);
}
});
- 我们得到请求数据。
- 我们验证数据
- 我们使用 更新记录
set(data, merge: true)
。这意味着它将仅更新通过数据参数传递的字段。
删除记录。
为了删除一场战斗,我们需要添加一个端点DELETE /fights/:id
。
app.delete('/fights/:id', async (request, response) => {
try {
const fightId = request.params.id;
if (!fightId) throw new Error('id is blank');
await db.collection('fights')
.doc(fightId)
.delete();
response.json({
id: fightId,
})
} catch(error){
response.status(500).send(error);
}
});
- 我们得到了战斗ID。
- 我们使用
delete()
它来删除文档实例(请记住,firestore 是基于文档的数据库(“NoSQL”))
我们的src/index.ts
文件应该是这样的
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import * as express from 'express';
import * as bodyParser from "body-parser";
admin.initializeApp(functions.config().firebase);
const db = admin.firestore(); // Add this
const app = express();
const main = express();
main.use('/api/v1', app);
main.use(bodyParser.json());
export const webApi = functions.https.onRequest(main);
app.get('/warmup', (request, response) => {
response.send('Warming up friend.');
});
app.post('/fights', async (request, response) => {
try {
const { winner, losser, title } = request.body;
const data = {
winner,
losser,
title
}
const fightRef = await db.collection('fights').add(data);
const fight = await fightRef.get();
response.json({
id: fightRef.id,
data: fight.data()
});
} catch(error){
response.status(500).send(error);
}
});
app.get('/fights/:id', async (request, response) => {
try {
const fightId = request.params.id;
if (!fightId) throw new Error('Fight ID is required');
const fight = await db.collection('fights').doc(fightId).get();
if (!fight.exists){
throw new Error('Fight doesnt exist.')
}
response.json({
id: fight.id,
data: fight.data()
});
} catch(error){
response.status(500).send(error);
}
});
app.get('/fights', async (request, response) => {
try {
const fightQuerySnapshot = await db.collection('fights').get();
const fights = [];
fightQuerySnapshot.forEach(
(doc) => {
fights.push({
id: doc.id,
data: doc.data()
});
}
);
response.json(fights);
} catch(error){
response.status(500).send(error);
}
});
app.put('/fights/:id', async (request, response) => {
try {
const fightId = request.params.id;
const title = request.body.title;
if (!fightId) throw new Error('id is blank');
if (!title) throw new Error('Title is required');
const data = {
title
};
const fightRef = await db.collection('fights')
.doc(fightId)
.set(data, { merge: true });
response.json({
id: fightId,
data
})
} catch(error){
response.status(500).send(error);
}
});
app.delete('/fights/:id', async (request, response) => {
try {
const fightId = request.params.id;
if (!fightId) throw new Error('id is blank');
await db.collection('fights')
.doc(fightId)
.delete();
response.json({
id: fightId,
})
} catch(error){
response.status(500).send(error);
}
});
测试
我们部署我们的功能。
firebase deploy
....
我们测试了所有端点。
# Testing create fight (POST /fights)
$ curl -d '{"winner":"levi", "losser":"henry", "title": "fight1"}' -H "Content-Type: application/json" -X POST "https://github-ring-levivm.firebaseapp.com/api/v1/fights/"
> {"id":"zC9QORei07hklkKUB1Gl","data":{"title":"fight1","winner":"levi","losser":"henry"}
# Testing get a fight (GET /fight:id)
$ curl -G "https://github-ring-levivm.firebaseapp.com/api/v1/fights/zC9QORei07hklkKUB1wGl/"
>{"id":"zC9QORei07hklkKUB1Gl","data":{"winner":"levi","losser":"henry","title":"fight1"}}
# Testing get fights list (GET /fights/)
$ curl -G "https://github-ring-levivm.firebaseapp.com/api/v1/fights/"
> [{"id":"zC9QORei07hklkKUB1Gl","data":{"title":"fight1","winner":"levi","losser":"henry"}}]
# Testing update a fight (PUT /fights/:id)
$ curl -d '{"title": "new fight title"}' -H "Content-Type: application/json" -X PUT "https://github-ring-levivm.firebaseapp.com/api/v1/fights/zC9QORei07hklkKUB1Gl/"
> {"id":"zC9QORei07hklkKUB1Gl","data":{"title":"new fight title"}}
# Testing delete a fight (DELETE /fight/:id)
$ curl -X DELETE "https://github-ring-levivm.firebaseapp.com/api/v1/fights/zC9QORei07hklkKUB1Gl/"
> {"id":"zC9QORei07hklkKUB1Gl"}
我们已经完成了,我们已经使用 Google Cloud Function(无服务器)构建了我们的 API Rest。
注意:您可以使用我们的 Firebase 控制台中的 Firestore 界面检查您的数据库。
如果这有帮助,请分享:)。
文章来源:https://dev.to/levivm/creating-a-serverless-rest-api-using-google-cloud-functions-firebasefirestore-in-10-min-37km如果您喜欢我的内容或者觉得有帮助,您可以给我买杯咖啡来激励我写更多内容