使用 Node.js (TypeScript) 和 Azure Functions 创建无服务器 REST API,并获得 CosmosDB 更改源作为奖励。

   2023-03-08 学习力0
核心提示:介绍在本文中,我们将使用 azure 函数和 ComsmosDB 创建一个无服务器 REST API 应用程序。使用的语言是 Node.js/TypeScript。此外,由于我们使用 CosmosDB 作为数据库,因此我们将使用 Azure 函数触发器接收 Change Feed 事件作为奖励。创建资源创建资源组az

Node.js(TypeScript)とAzure FunctionsでサーバレスなREST APIを作り、おまけにCosmosDBのChange Feedを受信してみる。

介绍

在本文中,我们将使用 azure 函数和 ComsmosDB 创建一个无服务器 REST API 应用程序。使用的语言是 Node.js/TypeScript。此外,由于我们使用 CosmosDB 作为数据库,因此我们将使用 Azure 函数触发器接收 Change Feed 事件作为奖励。

创建资源

创建资源组

az group create 
        --name ${AZ_RESOURCE_GROUP} 
        --location ${AZ_RESOURCE_GROUP_LOCATION}

创建 Azure Function App 资源

在创建 Azure Function App 资源之前,我们需要创建一个 Azure 存储帐户来支持 blob、队列和表存储。

az storage account create 
    --name ${AZ_STORAGE_ACCOUNT} 
    --location ${AZ_RESOURCE_GROUP_LOCATION} 
    --resource-group ${AZ_RESOURCE_GROUP} 
    --sku Standard_LRS

将上面创建的 Azure 存储帐户的名称作为参数传递给 --storage-account,以创建 Azure Function App 资源。

az functionapp create 
    --name ${AZ_FUNCTION} 
    --storage-account ${AZ_STORAGE_ACCOUNT} 
    --consumption-plan-location ${AZ_RESOURCE_GROUP_LOCATION} 
    --resource-group ${AZ_RESOURCE_GROUP} 
    --functions-version 4 
    --runtime node

创建 CosmosDB 资源

创建 CosmosDB 资源。始终将--enable-free-tier 传递给true 用于演示或测试目的。

az cosmosdb create 
    --resource-group ${AZ_RESOURCE_GROUP} 
    --name ${AZ_COSMOS_DB} 
    --enable-free-tier true 
    --enable-analytical-storage false

创建 Azure Function 本地项目

创建项目文件夹

mkdir az-function-rest-api
cd az-function-rest-api
npm init -y

安装库

npm install --save-exact @azure/cosmos

安装开发库

npm install --save-exact -D @azure/functions @types/node azure-functions-core-tools typescript

生成 Azure 函数项目

npx func init --worker-runtime node --language typescript

创建 Http 触发函数

这次我们将创建 4 个函数。

npx func new --name get-todos --template 'HTTP trigger' --authlevel 'anonymous' --language typescript
npx func new --name post-todos --template 'HTTP trigger' --authlevel 'anonymous' --language typescript
npx func new --name delete-todos --template 'HTTP trigger' --authlevel 'anonymous' --language typescript
npx func new --name patch-todos --template 'HTTP trigger' --authlevel 'anonymous' --language typescript

编辑 package.json

包.json
{
  "name": "az-function-rest-api",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "tsc", # <= 追加
    "watch": "tsc -w", # <= 追加
    "prestart": "npm run build", # <= 追加
    "start": "func start" # <= 追加
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@azure/cosmos": "3.17.1"
  },
  "devDependencies": {
    "@azure/functions": "3.2.0",
    "@types/node": "18.11.5",
    "azure-functions-core-tools": "4.0.4829",
    "typescript": "4.8.4"
  }
}

当前项目结构

.
├── delete-todos
│   ├── function.json
│   └── index.ts
├── get-todos
│   ├── function.json
│   └── index.ts
├── host.json
├── local.settings.json
├── package-lock.json
├── package.json
├── patch-todos
│   ├── function.json
│   └── index.ts
├── post-todos
│   ├── function.json
│   └── index.ts
└── tsconfig.json

在本地运行 Azure 函数

npm run start

默认情况下,每个函数的文件夹名称会自动设置为端点路径。

❯ npm run start

> az-function-rest-api@1.0.0 prestart
> npm run build


> az-function-rest-api@1.0.0 build
> tsc


> az-function-rest-api@1.0.0 start
> func start


Azure Functions Core Tools
Core Tools Version:       4.0.4829 Commit hash: N/A  (64-bit)
Function Runtime Version: 4.11.2.19273

[2022-10-25T05:54:42.841Z] Worker process started and initialized.

Functions:

        delete-todos: [GET,POST] http://localhost:7071/api/delete-todos

        get-todos: [GET,POST] http://localhost:7071/api/get-todos

        patch-todos: [GET,POST] http://localhost:7071/api/patch-todos

        post-todos: [GET,POST] http://localhost:7071/api/post-todos

当我实际发送 HTTP 请求时,响应会正确返回。

curl "http://localhost:7071/api/delete-todos?name=azure"
❯ curl "http://localhost:7071/api/get-todos?name=azure"
Hello, azure. This HTTP triggered function executed successfully.%

端点路径和 HTTP 方法可以从每个函数文件夹中自动生成的function.json 中设置。

  • 您可以在bindings.methods 列表中指定要允许的HTTP 方法。
  • 您可以使用bidnings.route 指定端点路径。路径参数也可以指定为占位符,并且可以从函数中引用。您可以通过添加? 使路径参数成为可选参数。
获取待办事项/function.json
{
  "bindings": [
    {
      "authLevel": "Anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": ["get"], # <= postを消去
      "route": "todos/{id?}" # <= 追加
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    }
  ],
  "scriptFile": "../dist/get-todos/index.js"
}

编辑get-todo/index.ts 以接收并查看路径参数。路径参数的数据存储在context 对象的bindingData 属性中,可以从函数的参数中接收。

获取待办事项/index.ts
import { AzureFunction, Context, HttpRequest } from "@azure/functions"

const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {
    const id = context.bindingData.id; // <= context.bindingData のなかにpath paramのデータが保管されている。

    context.res = {
        body: {
            id
        }
    };

};

export default httpTrigger;

再次运行该函数,可以看到function.json中设置的内容已经体现出来了。

npm run start
❯ npm run start

> az-function-rest-api@1.0.0 prestart
> npm run build


> az-function-rest-api@1.0.0 build
> tsc


> az-function-rest-api@1.0.0 start
> func start


Azure Functions Core Tools
Core Tools Version:       4.0.4829 Commit hash: N/A  (64-bit)
Function Runtime Version: 4.11.2.19273

[2022-10-25T06:16:51.255Z] Worker process started and initialized.

Functions:

        delete-todos: [GET,POST] http://localhost:7071/api/delete-todos

        get-todos: [GET] http://localhost:7071/api/todos/{id?} # <= 設定が反映されている。

        patch-todos: [GET,POST] http://localhost:7071/api/patch-todos

        post-todos: [GET,POST] http://localhost:7071/api/post-todos

如果将路径参数传递给路由并发送 HTTP 请求,则可以确认返回带有路径参数的 id

curl -i -X GET http://localhost:7071/api/todos/123
❯ curl -i -X GET http://localhost:7071/api/todos/123
{
  "id": 123
}%

创建 REST API

完成的项目结构

.
├── delete-todos
│   ├── function.json
│   └── index.ts
├── get-todos
│   ├── function.json
│   └── index.ts
├── host.json
├── lib
│   ├── db
│   │   └── db-config.ts
│   ├── dtos
│   │   ├── CreateTodoDto.ts
│   │   └── UpdateTodoDto.ts
│   ├── errors
│   │   └── HttpError.ts
│   ├── models
│   │   └── TodoItem.ts
│   ├── repositories
│   │   └── todo-repository.ts
│   ├── services
│   │   └── todo-service.ts
│   └── utils
│       └── generate-id.ts
├── local.settings.json
├── package-lock.json
├── package.json
├── patch-todos
│   ├── function.json
│   └── index.ts
├── post-todos
│   ├── function.json
│   └── index.ts
└── tsconfig.json

获取 todos 函数

获取待办事项/index.ts
import { AzureFunction, Context, HttpRequest } from "@azure/functions";
import { HttpError } from "../lib/errors/HttpError";
import { todoService } from "../lib/services/todo-service";

const httpTrigger: AzureFunction = async function (
  context: Context,
  req: HttpRequest
): Promise<void> {
  const todoId = context.bindingData.id;

  try {
    if (todoId) {
      const result = await todoService.getOne(todoId);
      context.res = {
        status: 200,
        body: result,
      };

      return;
    }
  } catch (err) {
    if (err instanceof HttpError) {
      context.res = {
        status: err.StatusCode,
        body: {
          error: {
            message: err.message,
          },
        },
      };
      return;
    }
  }

  const result = await todoService.getOnes();

  context.res = {
    status: 200,
    body: result,
  };
};

export default httpTrigger;
获取待办事项/function.json
{
  "bindings": [
    {
      "authLevel": "Anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": ["get"],
      "route": "todos/{id?}"
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    }
  ],
  "scriptFile": "../dist/get-todos/index.js"
}

发布待办事项功能

待办事项/index.ts
import { AzureFunction, Context, HttpRequest } from "@azure/functions";
import { todoService } from "../lib/services/todo-service";

const httpTrigger: AzureFunction = async function (
  context: Context,
  req: HttpRequest
): Promise<void> {
  const todoCreateDto = req.body;
  const result = await todoService.createOne(todoCreateDto);

  context.res = {
    status: 201,
    body: result,
  };
};

export default httpTrigger;
待办事项后/function.json
{
  "bindings": [
    {
      "authLevel": "Anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": ["post"],
      "route": "todos"
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    }
  ],
  "scriptFile": "../dist/post-todos/index.js"
}

删除待办事项功能

删除待办事项/index.ts
import { AzureFunction, Context, HttpRequest } from "@azure/functions";
import { HttpError } from "../lib/errors/HttpError";
import { todoService } from "../lib/services/todo-service";

const httpTrigger: AzureFunction = async function (
  context: Context,
  req: HttpRequest
): Promise<void> {
  const todoId = context.bindingData.id;

  try {
    await todoService.deleteOne(todoId);
  } catch (err) {
    if (err instanceof HttpError) {
      context.res = {
        status: err.StatusCode,
        body: {
          error: {
            message: err.message,
          },
        },
      };
      return;
    }
  }

  context.res = {
    status: 204,
  };
};

export default httpTrigger;
删除待办事项/function.json
{
  "bindings": [
    {
      "authLevel": "Anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": ["delete"],
      "route": "todos/{id}"
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    }
  ],
  "scriptFile": "../dist/delete-todos/index.js"
}

补丁待办事项功能

补丁待办事项/index.ts
import { AzureFunction, Context, HttpRequest } from "@azure/functions";
import { HttpError } from "../lib/errors/HttpError";
import { todoService } from "../lib/services/todo-service";

const httpTrigger: AzureFunction = async function (
  context: Context,
  req: HttpRequest
): Promise<void> {
  const todoId = context.bindingData.id;
  const updateTodoDto = req.body;

  try {
    await todoService.updateOne(updateTodoDto, todoId);
  } catch (err) {
    if (err instanceof HttpError) {
      context.res = {
        status: err.StatusCode,
        body: {
          error: {
            message: err.message,
          },
        },
      };
      return;
    }
  }

  context.res = {
    status: 204,
  };
};

export default httpTrigger;
补丁待办事项/function.json
{
  "bindings": [
    {
      "authLevel": "Anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": ["patch"],
      "route": "todos/{id}"
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    }
  ],
  "scriptFile": "../dist/patch-todos/index.js"
}

通用模块

模型

lib/models/TodoItem.ts
export interface TodoItem {
  id?: string;
  title: string;
  isCompleted: boolean;
}

服务

lib/services/todo-service.ts
import { CreateTodoDto } from "../dtos/CreateTodoDto";
import { UpdateTodoDto } from "../dtos/UpdateTodoDto";
import { HttpError } from "../errors/HttpError";
import { TodoItem } from "../models/TodoItem";
import { todoRepository } from "../repositories/todo-repository";
import { generateId } from "../utils/generate-id";

const getOnes = async (): Promise<TodoItem[] | any> => {
  return await todoRepository.getOnes();
};

const getOne = async (id: string): Promise<any> => {
  return await todoRepository.getOneById(id);
};

const createOne = async (dto: CreateTodoDto): Promise<TodoItem> => {
  const newTodo: TodoItem = {
    id: generateId(),
    ...dto,
    isCompleted: false,
  };

  return await todoRepository.createOne(newTodo);
};

const deleteOne = async (id: string) => {
  if (!id) {
    throw new HttpError("Todo item id is not provided", 400);
  }

  const existingTodo = await todoRepository.getOneById(id);
  if (!existingTodo) {
    throw new HttpError(`TodoItem(id=${id}) is not found.`, 404);
  }

  todoRepository.removeOneById(existingTodo.id);
};

const updateOne = async (dto: UpdateTodoDto, id: string) => {
  if (!id) {
    throw new HttpError("Todo item id is not provided", 400);
  }

  const existingTodo = await todoRepository.getOneById(id);

  if (!existingTodo) {
    throw new HttpError(`TodoItem(id=${id}) is not found.`, 404);
  }

  if (typeof dto.title !== "undefined") {
    existingTodo.title = dto.title;
  }

  if (typeof dto.isCompleted !== "undefined") {
    existingTodo.isCompleted = dto.isCompleted;
  }

  await todoRepository.update(existingTodo);
};

export const todoService = Object.freeze({
  getOnes,
  getOne,
  createOne,
  deleteOne,
  updateOne,
});

存储库

lib/repositories/todo-repository.ts
import { initDB } from "../db/db-config";

const db = initDB();

const getOnes = async () => {
  const { container } = await db;
  const { resources } = await container.items.readAll().fetchAll();
  return resources;
};

const getOneById = async (id: string) => {
  const { container } = await db;
  const { resource } = await container.item(id, id).read();
  return resource;
};

const createOne = async (todo: any) => {
  const { container } = await db;
  const { resource } = await container.items.create(todo);
  return resource;
};

const removeOneById = async (id: string) => {
  const { container } = await db;
  await container.item(id, id).delete();
};

const update = async (todo: any) => {
  const { container } = await db;
  await container.item(todo.id, todo.id).replace(todo);
};

export const todoRepository = Object.freeze({
  getOnes,
  getOneById,
  createOne,
  removeOneById,
  update,
});

数据库设置

lib/db/db-config.ts
import { CosmosClient } from "@azure/cosmos";

const cosmosConfig = {
  endpoint: process.env.COSMOSDB_URI,
  primaryKey: process.env.COSMOSDB_PRIMARY_KEY,
  database: process.env.COSMOSDB_DATABASE,
  container: process.env.COSMOSDB_CONTAINER,
  partitionKey: {
    paths: ["/id"],
  },
};

export const initDB = async () => {
  const cosmosClient = new CosmosClient({
    endpoint: cosmosConfig.endpoint,
    key: cosmosConfig.primaryKey,
  });
  const { database } = await cosmosClient.databases.createIfNotExists({
    id: cosmosConfig.database,
  });

  const { container } = await database.containers.createIfNotExists({
    id: cosmosConfig.container,
    partitionKey: cosmosConfig.partitionKey,
  });

  return {
    cosmosClient,
    database,
    container,
  };
};

自定义错误

lib/errors/HttpError.ts
export class HttpError extends Error {
  statusCode;
  constructor(message, statusCode) {
    super(message);
    this.statusCode = statusCode;
  }

  get StatusCode() {
    return this.statusCode;
  }
}

效用

lib/utils/generate-id.ts
import * as crypto from "crypto";

export const generateId = () => crypto.randomUUID();

其他配置文件

主机.json
{
  "version": "2.0",
  "logging": {
    "applicationInsights": {
      "samplingSettings": {
        "isEnabled": true,
        "excludedTypes": "Request"
      }
    }
  },
  "extensionBundle": {
    "id": "Microsoft.Azure.Functions.ExtensionBundle",
    "version": "[3.*, 4.0.0)"
  }

您可以使用local.settings.json 设置环境变量。添加COSMOSDB_URICOSMOSDB_PRIMARY_KEY 作为环境变量以连接到CosmosDB。为COSMOSDB_DATABASECOSMOSDB_CONTAINER指定任意值。

local.settings.json
{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "",
    "FUNCTIONS_WORKER_RUNTIME": "node",
    "COSMOSDB_URI": "https://<COSMOSDB_RESOURCE_NAME>.documents.azure.com:443/",
    "COSMOSDB_PRIMARY_KEY": "<COSMOSDB_PRIMARY_KEY>",
    "COSMOSDB_DATABASE": "node_azure_functions_db",
    "COSMOSDB_CONTAINER": "todos"
  }
}

COSMOSDB_URICOSMOSDB_PRIMARY_KEY 的值可以在 azure 门户中的“CosmosDB 资源 => Settgins => Keys”中查看。
Node.js(TypeScript)とAzure FunctionsでサーバレスなREST APIを作り、おまけにCosmosDBのChange Feedを受信してみる。

或者您可以使用以下命令获取它。

CosmosDB URI

az cosmosdb show 
    --resource-group ${AZ_RESOURCE_GROUP} 
    --name ${AZ_COSMOS_DB} 
    -o tsv 
    --query "documentEndpoint"

CosmosDB 主键

az cosmosdb keys list 
    --resource-group ${AZ_RESOURCE_GROUP} 
    --name ${AZ_COSMOS_DB} 
    --type "keys" 
    -o tsv 
    --query "primaryMasterKey"

调用 REST API

npm run start
❯ npm run start

> az-function-rest-api@1.0.0 prestart
> npm run build


> az-function-rest-api@1.0.0 build
> tsc


> az-function-rest-api@1.0.0 start
> func start


Azure Functions Core Tools
Core Tools Version:       4.0.4829 Commit hash: N/A  (64-bit)
Function Runtime Version: 4.11.2.19273

[2022-10-26T02:25:55.113Z] Worker process started and initialized.

Functions:

        delete-todos: [DELETE] http://localhost:7071/api/todos/{id}

        get-todos: [GET] http://localhost:7071/api/todos/{id?}

        patch-todos: [PATCH] http://localhost:7071/api/todos/{id}

        post-todos: [POST] http://localhost:7071/api/todos

实际上,点击以下命令来测试 API。

curl -i -X POST -d '{"title":"todo 1"}' http://localhost:7071/api/todos
curl -i -X GET http://localhost:7071/api/todos
curl -i -X GET http://localhost:7071/api/todos/{id}
curl -i -X PATCH -d '{"title":"todo 1 updated"}' http://localhost:7071/api/todos/{id}
curl -i -X DELETE http://localhost:7071/api/todos/{id}

添加待办事项

❯ curl -i -X POST -d '{"title":"todo 1"}' http://localhost:7071/api/todos
HTTP/1.1 201 Created
Content-Type: text/plain; charset=utf-8
Date: Wed, 26 Oct 2022 02:53:40 GMT
Server: Kestrel
Transfer-Encoding: chunked

{
  "id": "10cb9c6f-6161-4798-8de5-e213a5e2be1d",
  "title": "todo 1",
  "isCompleted": false,
  "_rid": "8T0VAJe9jpsDAAAAAAAAAA==",
  "_self": "dbs/8T0VAA==/colls/8T0VAJe9jps=/docs/8T0VAJe9jpsDAAAAAAAAAA==/",
  "_etag": ""c001744e-0000-2300-0000-6358a1350000"",
  "_attachments": "attachments/",
  "_ts": 1666752821
}%  

获取待办事项

❯ curl -i -X GET http://localhost:7071/api/todos
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Wed, 26 Oct 2022 02:55:41 GMT
Server: Kestrel
Transfer-Encoding: chunked

[
  {
    "id": "10cb9c6f-6161-4798-8de5-e213a5e2be1d",
    "title": "todo 1",
    "isCompleted": false,
    "_rid": "8T0VAJe9jpsDAAAAAAAAAA==",
    "_self": "dbs/8T0VAA==/colls/8T0VAJe9jps=/docs/8T0VAJe9jpsDAAAAAAAAAA==/",
    "_etag": ""c001744e-0000-2300-0000-6358a1350000"",
    "_attachments": "attachments/",
    "_ts": 1666752821
  }
]% 

编辑待办事项

❯ curl -i -X PATCH -d '{"title":"todo 1 updated"}' http://localhost:7071/api/todos/10cb9c6f-6161-4798-8de5-e213a5e2be1d
HTTP/1.1 204 No Content
Content-Type: text/plain; charset=utf-8
Date: Wed, 26 Oct 2022 02:56:31 GMT
Server: Kestrel

通过 ID 获取待办事项

❯ curl -i -X GET http://localhost:7071/api/todos/10cb9c6f-6161-4798-8de5-e213a5e2be1d
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Wed, 26 Oct 2022 02:57:34 GMT
Server: Kestrel
Transfer-Encoding: chunked

{
  "id": "10cb9c6f-6161-4798-8de5-e213a5e2be1d",
  "title": "todo 1 updated",
  "isCompleted": false,
  "_rid": "8T0VAJe9jpsDAAAAAAAAAA==",
  "_self": "dbs/8T0VAA==/colls/8T0VAJe9jps=/docs/8T0VAJe9jpsDAAAAAAAAAA==/",
  "_etag": ""c0013f9b-0000-2300-0000-6358a1e00000"",
  "_attachments": "attachments/",
  "_ts": 1666752992
}%

确认数据已添加到 Azure 门户端的 CosmosDB。
Node.js(TypeScript)とAzure FunctionsでサーバレスなREST APIを作り、おまけにCosmosDBのChange Feedを受信してみる。

创建更改提要侦听器函数

npx func new --name change-feed-listener --template 'Azure Cosmos DB trigger' --language typescript
.
├── change-feed-listener # <= 追加
│   ├── function.json
│   └── index.ts
├── delete-todos
│   ├── function.json
│   └── index.ts
├── get-todos
│   ├── function.json
│   └── index.ts
├── host.json
├── lib
│   ├── db
│   │   └── db-config.ts
│   ├── dtos
│   │   ├── CreateTodoDto.ts
│   │   └── UpdateTodoDto.ts
│   ├── errors
│   │   └── HttpError.ts
│   ├── models
│   │   └── TodoItem.ts
│   ├── repositories
│   │   └── todo-repository.ts
│   ├── services
│   │   └── todo-service.ts
│   └── utils
│       └── generate-id.ts
├── local.settings.json
├── package-lock.json
├── package.json
├── patch-todos
│   ├── function.json
│   └── index.ts
├── post-todos
│   ├── function.json
│   └── index.ts
└── tsconfig.json

cosmosDBTrigger 在连接的 CosmosDB 中添加或编辑数据时将执行该函数,并传递该记录的数据。擦除数据时不执行cosmosDBTrigger

更改提要监听器/index.ts
import { AzureFunction, Context } from "@azure/functions"

const cosmosDBTrigger: AzureFunction = async function (context: Context, documents: any[]): Promise<void> {
    if (!!documents && documents.length > 0) {
        context.log('Document:', documents);
    }
}

export default cosmosDBTrigger;

如果将connectionStringSetting 的值直接替换为连接字符串的值,则会发生错误。将实际值写入local.settings.json,并将connectionStringSetting 的值保留为COSMOSDB_CONNECTION_STRING_DOCUMENTDB

更改馈送侦听器/function.json
{
  "bindings": [
    {
      "type": "cosmosDBTrigger",
      "name": "documents",
      "direction": "in",
      "leaseCollectionName": "leases",
      "connectionStringSetting": "COSMOSDB_CONNECTION_STRING_DOCUMENTDB", # <= local.settings.jsonの値を参照。
      "databaseName": "node_azure_functions_db",
      "collectionName": "todos",
      "createLeaseCollectionIfNotExists": true
    }
  ],
  "scriptFile": "../../dist/src/change-feed-listener/index.js"
}

编辑local.settings.json 并添加COSMOSDB_CONNECTION_STRING_DOCUMENTDBCOSMOSDB_CONNECTION_STRING_DOCUMENTDB 的值是COSMOSDB_URICOSMOSDB_PRIMARY_KEY 的组合。请务必在键名末尾添加_DOCUMENTDB(需要引用来自function.json 的值)。

local.settings.json
{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "",
    "FUNCTIONS_WORKER_RUNTIME": "node",
    "COSMOSDB_URI": "https://<COSMOSDB_NAME>.documents.azure.com:443/",
    "COSMOSDB_PRIMARY_KEY": "<COSMOSDB_PRIMARY_KEY>",
    "COSMOSDB_DATABASE": "node_azure_functions_db",
    "COSMOSDB_CONTAINER": "todos"
    # 追加
    "COSMOSDB_CONNECTION_STRING_DOCUMENTDB": "AccountEndpoint=https://<COSMOSDB_NAME>.documents.azure.com:443/;AccountKey=<COSMOSDB_PRIMARY_KEY>;"
  }
}

或者您可以从 azure 门户中的“CosmosDB 资源 => Settgins => Keys”进行检查。
Node.js(TypeScript)とAzure FunctionsでサーバレスなREST APIを作り、おまけにCosmosDBのChange Feedを受信してみる。

npm run start

添加了一个新的change-feed-listener: cosmosDBTrigger 作为函数。

❯ npm run start

> az-function-rest-api@1.0.0 prestart
> npm run build


> az-function-rest-api@1.0.0 build
> tsc


> az-function-rest-api@1.0.0 start
> func start


Azure Functions Core Tools
Core Tools Version:       4.0.4829 Commit hash: N/A  (64-bit)
Function Runtime Version: 4.11.2.19273

[2022-10-26T05:15:06.386Z] Worker process started and initialized.

Functions:

        delete-todos: [DELETE] http://localhost:7071/api/todos/{id}

        get-todos: [GET] http://localhost:7071/api/todos/{id?}

        patch-todos: [PATCH] http://localhost:7071/api/todos/{id}

        post-todos: [POST] http://localhost:7071/api/todos

        change-feed-listener: cosmosDBTrigger # <= 追加

当我实际添加数据时。

❯ curl -i -X POST -d '{"title":"todo 1"}' http://localhost:7071/api/todos
HTTP/1.1 201 Created
Content-Type: text/plain; charset=utf-8
Date: Wed, 26 Oct 2022 05:40:11 GMT
Server: Kestrel
Transfer-Encoding: chunked

{
  "id": "29c84398-6b37-4dfd-ad8b-45403a62f4d7",
  "title": "todo 1",
  "isCompleted": false,
  "_rid": "8T0VAJe9jpsEAAAAAAAAAA==",
  "_self": "dbs/8T0VAA==/colls/8T0VAJe9jps=/docs/8T0VAJe9jpsEAAAAAAAAAA==/",
  "_etag": ""cf01af89-0000-2300-0000-6358c83b0000"",
  "_attachments": "attachments/",
  "_ts": 1666762811
}%

change-feed-listener 日志显示从更改源接收到的数据,确认 change-feed-listener 函数已成功执行。

[2022-10-26T05:40:16.500Z] Executing 'Functions.change-feed-listener' (Reason='New changes on collection todos at 2022-10-26T05:40:16.4919090Z', Id=50591fbf-9ef8-49cc-b34a-abbdca3631d1)
[2022-10-26T05:40:16.519Z] Document: [
[2022-10-26T05:40:16.519Z]   {
[2022-10-26T05:40:16.519Z]     id: '29c84398-6b37-4dfd-ad8b-45403a62f4d7',
[2022-10-26T05:40:16.519Z]     _rid: '8T0VAJe9jpsEAAAAAAAAAA==',
[2022-10-26T05:40:16.519Z]     _self: 'dbs/8T0VAA==/colls/8T0VAJe9jps=/docs/8T0VAJe9jpsEAAAAAAAAAA==/',
[2022-10-26T05:40:16.519Z]     _ts: 1666762811,
[2022-10-26T05:40:16.519Z]     _etag: '"cf01af89-0000-2300-0000-6358c83b0000"',
[2022-10-26T05:40:16.519Z]     title: 'todo 1',
[2022-10-26T05:40:16.519Z]     isCompleted: false,
[2022-10-26T05:40:16.519Z]     _lsn: 11
[2022-10-26T05:40:16.519Z]   }
[2022-10-26T05:40:16.519Z] ]

功能部署

只需在项目根目录执行以下命令即可轻松完成部署。

npx func azure functionapp publish ${AZ_FUNCTION}
❯ npx func azure functionapp publish myTestFunction231917910
Setting Functions site property 'netFrameworkVersion' to 'v6.0'
Getting site publishing info...
Creating archive for current directory...
Uploading 1.61 MB [#########################################################]
Upload completed successfully.
Deployment completed successfully.
Syncing triggers...
Functions in myTestFunction231917910:
    change-feed-listener - [cosmosDBTrigger]

    delete-todos - [httpTrigger]
        Invoke url: https://mytestfunction231917910.azurewebsites.net/api/todos/{id}

    get-todos - [httpTrigger]
        Invoke url: https://mytestfunction231917910.azurewebsites.net/api/todos/{id?}

    patch-todos - [httpTrigger]
        Invoke url: https://mytestfunction231917910.azurewebsites.net/api/todos/{id}

    post-todos - [httpTrigger]
        Invoke url: https://mytestfunction231917910.azurewebsites.net/api/todos

您可以使用以下命令查看已部署函数的日志。

npx func azure functionapp logstream ${AZ_FUNCTION}

结尾

我能够使用 Node.js (TypeScript) 和 Azure Functions 创建一个无服务器 REST API 应用程序。如果使用 azure 函数,则可以高速开发应用程序。部署也很容易。您可以轻松地接收来自 CosmosDB 的更改源,因此您可以轻松地添加以 azure 函数为中心的微服务应用程序。


原创声明:本文系作者授权爱码网发表,未经许可,不得转载;

原文地址:https://www.likecs.com/show-308631507.html

 
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • 项目中使用TypeScript的TodoList实例详解
    项目中使用TypeScript的TodoList实例详解
    目录为什么用todolisttodolist的ts化数据到视图实现handleTodoItemreadonly分类交叉类型新增功能联合类型可选属性数据转视图总结为什么用todolist现代的框架教程目前再也不是写个hello world那么简单了,而是需要有一定基础能力能够做到数据绑定、遍历、条件
    03-16
  • React之 hook / class 结合typescript笔记
    React之 hook / class 结合typescript笔记
    使用 create-react-app 开启 TypeScriptCreate React App 是一个官方支持的创建 React 单页应用程序的CLI,它提供了一个零配置的现代构建设置。当你使用 Create React App 来创建一个新的 TypeScript React 工程时,你可以运行 npx create-react-app my-app
    03-08
  • Angular基础(三) TypeScript
    Angular基础(三) TypeScript
       一、模仿Reddita) 运行ng new –ng4angular-reddit创建应用,从随书代码中复制样式文件,新建组件app-root,代码为:界面可以看到了:b) 对于界面输入的数据,获取的方式有点特别,使用了#newlink这样的语法,newlink是一个对象,现在代表就是所在的inp
    03-08
  • electron教程(番外篇二): 使用TypeScript版本的
    electron教程(一): electron的安装和项目的创建electron教程(番外篇一): 开发环境及插件, VSCode调试, ESLint + Google JavaScript Style Guide代码规范electron教程(番外篇二): 使用TypeScript版本的electron, VSCode调试TypeScript, TS版本的ESLintelectron
    03-08
  • 使用 ESModule 和 TypeScript 构建 Node.js 环境
    使用 ESModule 和 TypeScript 构建 Node.js 环
    介绍由于我经常使用 React,所以我在前端接触过 Node.js,但我从未接触过后端。正常搭建环境的时候,不能使用import语句,变成了require语句,很不方便。我认为有各种各样的错误,所以如果你能指出它们,我将不胜感激。执行环境macOS 蒙特雷 ver12.5.1MacBook
    03-08
  • [个人发展] 我做了一个可以永远谈论任何事情的女士对话AI(TypeScript,Python)
    [个人发展] 我做了一个可以永远谈论任何事情的
    在个人发展中对话式人工智能服务 Eveki我做了虚构角色1这是一项以人工智能为特色的服务,可以再现并享受自然对话。这一次,作为第一个艾小姐发表了。请先尝试实物。服务概览与人工智能对话基本上只需输入您的信息是。对话是用女士的语言进行的,就像人类一样
    03-08
  • 安装 TypeScript 并编译成JS
    安装 TypeScript 并编译成JS
    官网: https://github.com/microsoft/TypeScriptTypeScript是一种由微软开发的开源、跨平台的编程语言。它是JavaScript的超集,最终会被编译为JavaScript代码。TypeScript是一种应用级JavaScript语言。TypeScript为JavaScript添加了可选类型,支持针对任何浏
    03-08
  • 使用TypeScript,AngularJs和Web API构建基本的C
    原文地址:using typescript with angularjs and web api 版权归其作者所有.在这篇文章中我将向大家展示如何使用TypeScript,Angular Js 和Asp.net Web API 来构建一个基本的实现CRUD功能的Web应用程序. Typescript提供了一系列的功能来方便程序员构造和组织更
    02-10
  • Typescript 中类的继承
    Typescript中类的定义与继承与后端开发语言java/C#等非常像,实现起来非常方便,而且代码便于阅读。用Typescript写较大项目时是非常有优势的。/** * BaseClass */class BaseClass {constructor(name:string,age:number) {this.name=name;this.age=age;}name:s
    02-09
  • TypeScript实现设计模式——工厂模式
    上回用typescript实现了单例模式,这回来实现工厂模式。工厂模式又分为简单工厂模式、工厂方法模式以及抽象工厂模式。简单工厂模式简单工厂模式通常在业务比较简单的情况下使用,它有三个部分组成:工厂类、抽象产品类、具体产品类。抽象产品类abstract class
    02-09
点击排行