使用 NodeJS 通过 Google Cloud Functions API REST 创建无服务器 REST API 的初学者指南,使用 Google Cloud Functions(无服务器)

2025-05-25

使用 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 作为我们的数据库。

图片1

要创建我们的数据库,请单击“开发” > “数据库”,然后选择“以测试模式启动”。

图片2

在本地初始化我们的项目

我们需要使用 NPM 安装 firebase。



npm install -g firebase-tools


Enter fullscreen mode Exit fullscreen mode

然后,让我们登录我们的firebase帐户。



firebase login
........... input credentials


Enter fullscreen mode Exit fullscreen mode

初始化项目



firebase init
........ select project


Enter fullscreen mode Exit fullscreen mode

图片3

它将提示一个交互式控制台。

  1. 选择功能和托管选项。
  2. 你想用什么语言来编写 Cloud Functions?TypeScript
  3. 你想使用 TSLint 来捕获可能的错误并强制执行代码风格吗?是的
  4. 您是否现在要使用 npm 安装依赖项?是的
  5. 您想使用哪个目录作为您的公共目录?按 Enter 键选择“公共”(这是默认选项)
  6. 配置为单页应用(将所有 URL 重写为 /index.html)?否

我们准备好了,我们的firebase项目已初始化。

安装 Express.js 和依赖项



cd functions
npm install --save express body-parser 


Enter fullscreen mode Exit fullscreen mode

创建我们的 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";


Enter fullscreen mode Exit fullscreen mode

初始化 Firebase 以访问其服务



admin.initializeApp(functions.config().firebase);


Enter fullscreen mode Exit fullscreen mode

初始化 Express.js 服务器



const app = express();
const main = express();


Enter fullscreen mode Exit fullscreen mode

配置服务器。

  • 让我们添加用于接收请求的路径。
  • 选择 JSON 作为处理请求主体的主要解析器。


main.use('/api/v1', app);
main.use(bodyParser.json());


Enter fullscreen mode Exit fullscreen mode

 导出我们的函数。

最后,同样重要的是,让我们定义 Google Cloud Function 的名称,我们将使用 来暴露它export。我们的函数将接收一个 Express 服务器对象(本例中为main),该对象将用于处理请求。如果您想了解更多关于其工作原理的信息,可以查看Stackoverflow 上的这个好答案。



export const webApi = functions.https.onRequest(main);


Enter fullscreen mode Exit fullscreen mode

创建我们的第一个服务

让我们公开一个仅返回字符串的 GET 端点。



app.get('/warm', (req, res) => {
    res.send('Calentando para la pelea');
})


Enter fullscreen mode Exit fullscreen mode

我们的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.');

})



Enter fullscreen mode Exit fullscreen mode

 部署我们的功能。

在部署之前,我们需要更改配置文件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


Enter fullscreen mode Exit fullscreen mode

我们已做好部署准备。



firebase deploy


Enter fullscreen mode Exit fullscreen mode


.....
✔  Deploy complete!

Project Console: https://console.firebase.google.com/project/github-ring-levivm/overview
Hosting URL: https://github-ring-levivm.firebaseapp.com


Enter fullscreen mode Exit fullscreen mode

如果一切正常,Hosting URL 将是我们的 Google Cloud Function 端点。

 测试我们的功能

让我们GET使用CURL



$ curl -G "https://github-ring-levivm.firebaseapp.com/api/v1/warmup"
Warming up friend.


Enter fullscreen mode Exit fullscreen mode

Rest API CRUD

让我们添加 CRUD 端点。我们将管理fights信息。

创建记录

首先,让我们初始化数据库。我们打开我们的src/index.ts,并在管理员初始化后添加这个




admin.initializeApp(functions.config().firebase);
const db = admin.firestore(); // Add this



Enter fullscreen mode Exit fullscreen mode

为了创建战斗记录,让我们创建POST /fights/端点。我们的战斗记录将包含winnerlosertitle



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);

  }
});


Enter fullscreen mode Exit fullscreen mode
  • 我们使用以下方式获取帖子数据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);

  }

});


Enter fullscreen mode Exit fullscreen mode
  • 我们使用 获取战斗 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);

  }

});



Enter fullscreen mode Exit fullscreen mode
  • 我们得到了一个集合快照。
  • 我们遍历每个文档并将数据推送到数组中。
  • 我们归还了我们的战斗名单。

 更新记录

我们必须创建一个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);

  }

});


Enter fullscreen mode Exit fullscreen mode
  • 我们得到请求数据。
  • 我们验证数据
  • 我们使用 更新记录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);

  }

});



Enter fullscreen mode Exit fullscreen mode
  • 我们得到了战斗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);

  }

});


Enter fullscreen mode Exit fullscreen mode

 测试

我们部署我们的功能。



firebase deploy
....


Enter fullscreen mode Exit fullscreen mode

我们测试了所有端点。



# 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"}



Enter fullscreen mode Exit fullscreen mode

我们已经完成了,我们已经使用 Google Cloud Function(无服务器)构建了我们的 API Rest。

注意:您可以使用我们的 Firebase 控制台中的 Firestore 界面检查您的数据库。

图片4

如果这有帮助,请分享:)。

如果您喜欢我的内容或者觉得有帮助,您可以给我买杯咖啡来激励我写更多内容

文章来源:https://dev.to/levivm/creating-a-serverless-rest-api-using-google-cloud-functions-firebasefirestore-in-10-min-37km
PREV
初级开发人员资源列表 软技能 技术技能
NEXT
编码最佳实践,第一章:函数。第 1 章:函数