分享好友 编程语言首页 频道列表

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

typescript文章/教程  2023-03-08 12:060

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

查看更多关于【typescript文章/教程】的文章

展开全文
相关推荐
反对 0
举报 0
评论 0
图文资讯
热门推荐
优选好物
更多热点专题
更多推荐文章
electron教程(番外篇二): 使用TypeScript版本的electron, VSCode调试TypeScript, TS版本的ESLint
electron教程(一): electron的安装和项目的创建electron教程(番外篇一): 开发环境及插件, VSCode调试, ESLint + Google JavaScript Style Guide代码规范electron教程(番外篇二): 使用TypeScript版本的electron, VSCode调试TypeScript, TS版本的ESLintelectron

0评论2023-03-08610

使用TypeScript,AngularJs和Web API构建基本的CRUD Web 应用
原文地址:using typescript with angularjs and web api 版权归其作者所有.在这篇文章中我将向大家展示如何使用TypeScript,Angular Js 和Asp.net Web API 来构建一个基本的实现CRUD功能的Web应用程序. Typescript提供了一系列的功能来方便程序员构造和组织更

0评论2023-02-10391

Typescript 中类的继承
Typescript中类的定义与继承与后端开发语言java/C#等非常像,实现起来非常方便,而且代码便于阅读。用Typescript写较大项目时是非常有优势的。/** * BaseClass */class BaseClass {constructor(name:string,age:number) {this.name=name;this.age=age;}name:s

0评论2023-02-09350

TypeScript实现设计模式——工厂模式
上回用typescript实现了单例模式,这回来实现工厂模式。工厂模式又分为简单工厂模式、工厂方法模式以及抽象工厂模式。简单工厂模式简单工厂模式通常在业务比较简单的情况下使用,它有三个部分组成:工厂类、抽象产品类、具体产品类。抽象产品类abstract class

0评论2023-02-09907

使用Visual Studio Code和typescript 开发调试React Native项目
关于React Native的详细介绍我就不叙述了,他是使用js构建原生app的开发框架。一次变编码多平台运行,非常强大。但是个人不喜欢js的过于灵活(弱类型)的语法。强大的强类型语言Typescript(简称TS)是我的首选,他可以编译成JavaScript,编译成的JavaScript代

0评论2023-02-09391

TypeScript的安装、使用及配置
JS是一种弱类型语言,对于代码的维护和重构是非常困难的。TypeScript是一个编译到纯JS的有类型定义的JS超集,可以极大的提升代码的健壮性。使用TS后,能够方便的查看函数定义、默认参数及类型、变量结构体等,同时对于IDE的参数类型提示也是非常友好的。优点

0评论2023-02-09713

vue-type-check: Vue 模板中的 Typescript 类型检查
越来越多人开始尝试使用 Typescript 编写他们的 vue 项目,vue 本身也在不断加强对 Typescript 的支持(官方提供 vue-class-component 库、使用 Typescript 编写 Vue 3.0 等),但是对于组件中模板部分的类型检查仍然有很大的局限性。为此我们开源了一个易

0评论2023-02-09756

JavaScript面向对象轻松入门之抽象(demo by ES5、ES6、TypeScript)
抽象的概念  狭义的抽象,也就是代码里的抽象,就是把一些相关联的业务逻辑分离成属性和方法(行为),这些属性和方法就可以构成一个对象。  这种抽象是为了把难以理解的代码归纳成与现实世界关联的概念,比如小狗这样一个对象:属性可以归纳出“毛色”、

0评论2023-02-09777

Angular2+typescript+webpack2(支持aot, tree shaking, lazy loading)
Angular2官方推荐的应该是使用systemjs加载, 但是当我使用到它的tree shaking的时候,发现如果使用systemjs+rollup,只能打包成一个文件,然后lazy loading就没法搞了。因此我使用了webpack2,webpack2自带tree shaking,只要将tsconfig中的module设置成es201

0评论2023-02-09811

Typescript学习笔记 TYPESCRIPT
TypeScript 是 JavaScript 的类型的超集,它可以编译成纯 JavaScript。 安装 TypeScript命令行工具安装:npm install -g typescript编译一个 TypeScript 文件:tsc hello.ts  原始数据类型/ 任意值为每一个变量提前申明该类型,则该变量取值只能为申明的

0评论2023-02-09688

typescript结合three.js
import * as THREE from "three";// https://github.com/pinqy520/three-typescript-starter/blob/master/src/index.tsclass Game{private _scene: THREE.Scene;//private _canvas: HTMLCanvasElement;private _camera: THREE.PerspectiveCamera;private _ren

0评论2023-02-09748

更多推荐