このクイック スタートでは、TypeSpec を使用して RESTful TypeScript API アプリケーションを設計、生成、実装する方法について説明します。 TypeSpec は、クラウド サービス API を記述するためのオープンソース言語であり、複数のプラットフォーム用のクライアントコードとサーバー コードを生成します。 このクイック スタートに従って、API コントラクトを 1 回定義し、一貫した実装を生成する方法について説明します。これにより、保守性が高く、文書化された API サービスを構築できます。
このクイック スタートでは次の作業を行います。
- TypeSpec を使用して API を定義する
- API サーバー アプリケーションを作成する
- Azure Cosmos DB を永続ストレージに統合する
- Azure にデプロイする
- API を実行してテストする
Important
@typespec/http-server-js
エミッターは現在プレビュー段階です。
この情報は、リリース前に大幅に変更される可能性があるプレリリース製品に関連しています。 Microsoft は、ここに記載されている情報に関して、明示または黙示を問わず、一切の保証を行いません。
Prerequisites
- アクティブな Azure アカウントアカウントがない場合、Azure 試用版にサインアップして、最大 10 件の無料 Mobile Apps を入手できます。 アカウントがない場合は、無料でアカウントを作成 します。
- Node.js LTS がシステムにインストールされています。
- TypeScript コードを記述およびコンパイルするための TypeScript。
- Docker
- Visual Studio Code
- TypeSpec extension
- 省略可能: Azure Developer CLI を使用したデプロイ
TypeSpec を使用した開発
TypeSpec は、言語に依存しない方法で API を定義し、複数のプラットフォーム用の API サーバーとクライアント ライブラリを生成します。 この機能を利用すると、次のことが可能になります。
- API コントラクトを 1 回定義する
- 一貫性のあるサーバーとクライアント のコードを生成する
- API インフラストラクチャではなくビジネス ロジックの実装に重点を置く
TypeSpec は API サービス管理を提供します。
- API 定義言語
- API 用のサーバー側ルーティング ミドルウェア
- API を使用するためのクライアント ライブラリ
クライアント要求とサーバー統合を指定します。
- データベース、ストレージ、メッセージング用の Azure サービスなどのミドルウェアにビジネス ロジックを実装する
- API のホスティング サーバー (ローカルまたは Azure)
- 繰り返し可能なプロビジョニングとデプロイのためのスクリプト
新しい TypeSpec アプリケーションを作成する
API サーバーと TypeSpec ファイルを保持する新しいフォルダーを作成します。
mkdir my_typespec_quickstart cd my_typespec_quickstart
TypeSpec コンパイラをグローバルにインストールします。
npm install -g @typespec/compiler
TypeSpec が正しくインストールされていることを確認します。
tsp --version
TypeSpec プロジェクトを初期化します。
tsp init
次のプロンプトには提供された答えで回答してください。
- ここで新しいプロジェクトを初期化しますか? Y
- プロジェクト テンプレートを選択しますか? 汎用 REST API
- プロジェクト名を入力します:ウィジェット
- どのエミッターを使用しますか?
- OpenAPI 3.1 ドキュメント
- JavaScript サーバー スタブ
TypeSpec エミッタ ーは、さまざまな TypeSpec コンパイラ API を利用して TypeSpec コンパイル プロセスを反映し、成果物を生成するライブラリです。
初期化が完了するまで待ってから続行します。
プロジェクトをコンパイルします。
tsp compile .
TypeSpec は、
./tsp-output
で既定のプロジェクトを生成し、2 つの個別のフォルダーを作成します。-
スキーマ は OpenApi 3 仕様です。
./main.tsp
の数行が、あなたのために200行を超えるOpenApi仕様を生成していることに注意してください。 -
サーバー は生成されたミドルウェアです。 このミドルウェアは、Node.js サーバー プロジェクトに組み込むことができます。
-
./tsp-output/js/src/generated/models/all/demo-service.ts
は Widgets API のインターフェイスを定義します。 -
./tsp-output/js/src/generated/http/openapi3.ts
では、Open API 仕様が TypeScript ファイルとして定義され、TypeSpec プロジェクトをコンパイルするたびに再生成されます。
-
-
スキーマ は OpenApi 3 仕様です。
TypeSpec エミッタを構成する
TypeSpec ファイルを使用して、Express.js サーバー全体をスキャフォールディングするように API サーバーの生成を構成します。
./tsconfig.yaml
を開き、既存の構成を次の YAML に置き換えます。emit: - "@typespec/openapi3" - "@typespec/http-server-js" options: "@typespec/openapi3": emitter-output-dir: "{output-dir}/server/schema" openapi-versions: - 3.1.0 "@typespec/http-server-js": emitter-output-dir: "{output-dir}/server" express: true
この構成により、完全な Express.js API サーバーが作成されます。
-
express
: Swagger UI を含む Express.js API サーバーを生成します。 -
emitter-output-dir
: すべてを./server
ディレクトリに生成します。
-
既存の
./tsp-output
を削除します。 心配しないで、次の手順でサーバーを生成します。TypeSpec JavaScript エミッタを使用して、Express.js サーバーを作成します。
npx hsjs-scaffold
新しい
./tsp-output/server
ディレクトリに移動します。cd ./tsp-output/server
TypeScript を JavaScript にコンパイルします。
tsc
プロジェクトを実行します。
npm start
通知が ブラウザーで開くのを待ちます。
ブラウザーを開き、
http://localhost:3000/.api-docs
に移動します。既定の TypeSpec API とサーバーはどちらも機能します。 この API サーバーを終了する場合は、
./tsp-output/server/src/controllers/widgets.ts
の Widgets API をサポートするビジネス ロジックを追加します。 UI は、ハードコーディングされた偽のデータを返す API に接続されています。
アプリケーション ファイルの構造を理解する
tsp-output/server/
にある Express.js プロジェクト構造には、生成されたサーバー、package.json、Azure 統合のミドルウェアが含まれます。
server
├── package.json
├── package-lock.json
├── src
│ ├── controllers
│ │ └── widgets.ts
│ ├── generated
│ │ ├── helpers
│ │ │ ├── datetime.ts
│ │ │ ├── header.ts
│ │ │ ├── http.ts
│ │ │ ├── multipart.ts
│ │ │ ├── router.ts
│ │ │ └── temporal
│ │ │ ├── native.ts
│ │ │ └── polyfill.ts
│ │ ├── http
│ │ │ ├── openapi3.ts
│ │ │ ├── operations
│ │ │ │ └── server-raw.ts
│ │ │ └── router.ts
│ │ └── models
│ │ └── all
│ │ ├── demo-service.ts
│ │ └── typespec.ts
│ ├── index.ts
│ └── swagger-ui.ts
親 TypeSpec プロジェクトのファイル構造には、次の Express.js プロジェクトが tsp-output
に含まれています。
├── tsp-output
├── .gitignore
├── main.tsp
├── package-lock.json
├── package.json
├── tspconfig.yaml
永続化を Azure Cosmos DB no-sql に変更する
基本的な Express.js API サーバーが動作するように、永続的なデータ ストア用に Azure Cosmos DB と連携するように Express.js サーバーを更新します。 これには、ミドルウェアで Cosmos DB 統合を使用するための index.ts
の変更が含まれます。 すべての変更は、 ./tsp-output/server/src/generated
ディレクトリの外部で行う必要があります。
./tsp-output/server
ディレクトリで、Azure Cosmos DB をプロジェクトに追加します。npm install @azure/cosmos
Azure に対して認証する Azure ID ライブラリを追加します。
npm install @azure/identity
Azure に固有のソース コードを保持する
./tsp-output/server/src/azure
ディレクトリを作成します。そのディレクトリに
cosmosClient.ts
ファイルを作成して Cosmos DB クライアント オブジェクトを作成し、次のコードを貼り付けます。import { CosmosClient, Database, Container } from "@azure/cosmos"; import { DefaultAzureCredential } from "@azure/identity"; /** * Interface for CosmosDB configuration settings */ export interface CosmosConfig { endpoint: string; databaseId: string; containerId: string; partitionKey: string; } /** * Singleton class for managing CosmosDB connections */ export class CosmosClientManager { private static instance: CosmosClientManager; private client: CosmosClient | null = null; private config: CosmosConfig | null = null; private constructor() {} /** * Get the singleton instance of CosmosClientManager */ public static getInstance(): CosmosClientManager { if (!CosmosClientManager.instance) { CosmosClientManager.instance = new CosmosClientManager(); } return CosmosClientManager.instance; } /** * Initialize the CosmosDB client with configuration if not already initialized * @param config CosmosDB configuration */ private ensureInitialized(config: CosmosConfig): void { if (!this.client || !this.config) { this.config = config; this.client = new CosmosClient({ endpoint: config.endpoint, aadCredentials: new DefaultAzureCredential(), }); } } /** * Get a database instance, creating it if it doesn't exist * @param config CosmosDB configuration * @returns Database instance */ private async getDatabase(config: CosmosConfig): Promise<Database> { this.ensureInitialized(config); const { database } = await this.client!.databases.createIfNotExists({ id: config.databaseId }); return database; } /** * Get a container instance, creating it if it doesn't exist * @param config CosmosDB configuration * @returns Container instance */ public async getContainer(config: CosmosConfig): Promise<Container> { const database = await this.getDatabase(config); const { container } = await database.containers.createIfNotExists({ id: config.containerId, partitionKey: { paths: [config.partitionKey] } }); return container; } /** * Clean up resources and close connections */ public dispose(): void { this.client = null; this.config = null; } } export const buildError = (error: any, message: string) => { const statusCode = error?.statusCode || 500; return { code: statusCode, message: `${message}: ${error?.message || 'Unknown error'}` }; };
ファイルでエンドポイント、データベース、コンテナーが使用されていることに注意してください。 Azure ID 資格情報
DefaultAzureCredential
を使用しているため、接続文字列やキーは必要ありません。 ローカル環境と運用環境の両方のセキュリティで保護された認証のこの方法の詳細について説明します。新しいウィジェット コントローラーを作成し、
./tsp-output/server/src/controllers/WidgetsCosmos.ts
し、Azure Cosmos DB の次の統合コードを貼り付けます。import { Widgets, Widget, WidgetList, AnalyzeResult,Error } from "../generated/models/all/demo-service.js"; import { WidgetMergePatchUpdate } from "../generated/models/all/typespec/http.js"; import { CosmosClientManager, CosmosConfig, buildError } from "../azure/cosmosClient.js"; import { HttpContext } from "../generated/helpers/router.js"; import { Container } from "@azure/cosmos"; export interface WidgetDocument extends Widget { _ts?: number; _etag?: string; } /** * Implementation of the Widgets API using Azure Cosmos DB for storage */ export class WidgetsCosmosController implements Widgets<HttpContext> { private readonly cosmosConfig: CosmosConfig; private readonly cosmosManager: CosmosClientManager; private container: Container | null = null; /** * Creates a new instance of WidgetsCosmosController * @param azureCosmosEndpoint Cosmos DB endpoint URL * @param databaseId The Cosmos DB database ID * @param containerId The Cosmos DB container ID * @param partitionKey The partition key path */ constructor(azureCosmosEndpoint: string, databaseId: string, containerId: string, partitionKey: string) { if (!azureCosmosEndpoint) throw new Error("azureCosmosEndpoint is required"); if (!databaseId) throw new Error("databaseId is required"); if (!containerId) throw new Error("containerId is required"); if (!partitionKey) throw new Error("partitionKey is required"); this.cosmosConfig = { endpoint: azureCosmosEndpoint, databaseId: databaseId, containerId: containerId, partitionKey: partitionKey }; this.cosmosManager = CosmosClientManager.getInstance(); } /** * Get the container reference, with caching * @returns The Cosmos container instance */ private async getContainer(): Promise<Container | null> { if (!this.container) { try { this.container = await this.cosmosManager.getContainer(this.cosmosConfig); return this.container; } catch (error: any) { console.error("Container initialization error:", error); throw buildError(error, `Failed to access container ${this.cosmosConfig.containerId}`); } } return this.container; } /** * Create a new widget * @param widget The widget to create * @returns The created widget with assigned ID */ async create(ctx: HttpContext, body: Widget ): Promise<Widget | Error> { const id = body.id; try { const container = await this.getContainer(); if(!container) { return buildError({statusCode:500}, "Container is not initialized"); } if (!body.id) { return buildError({statusCode:400}, "Widget ID is required"); } const response = await container.items.create<Widget>(body, { disableAutomaticIdGeneration: true }); if (!response.resource) { return buildError({statusCode:500}, `Failed to create widget ${body.id}: No resource returned`); } return this.documentToWidget(response.resource); } catch (error: any) { if (error?.statusCode === 409) { return buildError({statusCode:409}, `Widget with id ${id} already exists`); } return buildError(error, `Failed to create widget ${id}`); } } /** * Delete a widget by ID * @param id The ID of the widget to delete */ async delete(ctx: HttpContext, id: string): Promise<void | Error> { try { const container = await this.getContainer(); if(!container) { return buildError({statusCode:500}, "Container is not initialized"); } await container.item(id, id).delete(); } catch (error: any) { if (error?.statusCode === 404) { return buildError({statusCode:404}, `Widget with id ${id} not found`); } return buildError(error, `Failed to delete widget ${id}`); } } /** * Get a widget by ID * @param id The ID of the widget to retrieve * @returns The widget if found */ async read(ctx: HttpContext, id: string): Promise<Widget | Error> { try { const container = await this.getContainer(); if(!container) { return buildError({statusCode:500}, "Container is not initialized"); } const { resource } = await container.item(id, id).read<WidgetDocument>(); if (!resource) { return buildError({statusCode:404}, `Widget with id ${id} not found`); } return this.documentToWidget(resource); } catch (error: any) { return buildError(error, `Failed to read widget ${id}`); } } /** * List all widgets with optional paging * @returns List of widgets */ async list(ctx: HttpContext): Promise<WidgetList | Error> { try { const container = await this.getContainer(); if(!container) { return buildError({statusCode:500}, "Container is not initialized"); } const { resources } = await container.items .query({ query: "SELECT * FROM c" }) .fetchAll(); return { items: resources.map(this.documentToWidget) }; } catch (error: any) { return buildError(error, "Failed to list widgets"); } } /** * Update an existing widget * @param id The ID of the widget to update * @param body The partial widget data to update * @returns The updated widget */ async update( ctx: HttpContext, id: string, body: WidgetMergePatchUpdate, ): Promise<Widget | Error> { try { const container = await this.getContainer(); if(!container) { return buildError({statusCode:500}, "Container is not initialized"); } // First check if the widget exists const { resource: item } = await container.item(id).read<WidgetDocument>(); if (!item) { return buildError({statusCode:404}, `Widget with id ${id} not found`); } // Apply patch updates to the existing widget const updatedWidget: Widget = { ...item, ...body, id }; // Replace the document in Cosmos DB const { resource } = await container.item(id).replace(updatedWidget); if (!resource) { return buildError({statusCode:500}, `Failed to update widget ${id}: No resource returned`); } return this.documentToWidget(resource); } catch (error: any) { return buildError(error, `Failed to update widget ${id}`); } } async analyze(ctx: HttpContext, id: string): Promise<AnalyzeResult | Error> { return { id: "mock-string", analysis: "mock-string", }; } /** * Convert a Cosmos DB document to a Widget */ private documentToWidget(doc: WidgetDocument): Widget { return Object.fromEntries( Object.entries(doc).filter(([key]) => !key.startsWith('_')) ) as Widget; } }
./tsp-output/server/src/index.ts
を更新して新しいコントローラーをインポートし、Azure Cosmos DB 環境設定を取得し、WidgetsCosmosController を作成してルーターに渡します。// Generated by Microsoft TypeSpec import { WidgetsCosmosController } from "./controllers/WidgetsCosmos.js"; import { createDemoServiceRouter } from "./generated/http/router.js"; import express from "express"; import morgan from "morgan"; import { addSwaggerUi } from "./swagger-ui.js"; const azureCosmosEndpoint = process.env.AZURE_COSMOS_ENDPOINT!; const azureCosmosDatabase = "WidgetDb"; const azureCosmosContainer = "Widgets"; const azureCosmosPartitionKey = "/Id"; const router = createDemoServiceRouter( new WidgetsCosmosController( azureCosmosEndpoint, azureCosmosDatabase, azureCosmosContainer, azureCosmosPartitionKey) ); const PORT = process.env.PORT || 3000; const app = express(); app.use(morgan("dev")); const SWAGGER_UI_PATH = process.env.SWAGGER_UI_PATH || "/.api-docs"; addSwaggerUi(SWAGGER_UI_PATH, app); app.use(router.expressMiddleware); app.listen(PORT, () => { console.log(`Server is running at http://localhost:${PORT}`); console.log( `API documentation is available at http://localhost:${PORT}${SWAGGER_UI_PATH}`, ); });
./tsp-output/server
のターミナルで、TypeScript を JavaScript にコンパイルします。tsc
これで、プロジェクトは Cosmos DB 統合でビルドされます。 Azure リソースを作成してプロジェクトをデプロイするデプロイ スクリプトを作成しましょう。
デプロイ インフラストラクチャを作成する
Azure Developer CLI と Bicep テンプレートを使用して、繰り返し可能なデプロイを行うために必要なファイルを作成します。
TypeSpec プロジェクトのルートで、
azure.yaml
配置定義ファイルを作成し、次のソースに貼り付けます。# yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/main/schemas/v1.0/azure.yaml.json name: azure-typespec-scaffold-js metadata: template: azd-init@1.14.0 services: api: project: ./ host: containerapp language: js docker: path: Dockerfile pipeline: provider: github hooks: postprovision: windows: shell: pwsh run: | # Set environment variables for the Container App azd env set AZURE_COSMOS_ENDPOINT "$env:AZURE_COSMOS_ENDPOINT" continueOnError: false interactive: true posix: shell: sh run: | # Set environment variables for the Container App azd env set AZURE_COSMOS_ENDPOINT "$AZURE_COSMOS_ENDPOINT" continueOnError: false interactive: true
この構成が TypeSpec プロジェクト全体を参照していることに注意してください。
TypeSpec プロジェクトのルートで、Azure Container Apps のコンテナーのビルドに使用する
./Dockerfile
を作成します。# Stage 1: Build stage FROM node:20-alpine AS builder WORKDIR /app # Install TypeScript globally RUN npm install -g typescript # Copy package files first to leverage Docker layer caching COPY package*.json ./ # Create the tsp-output/server directory structure RUN mkdir -p tsp-output/server # Copy server package.json COPY tsp-output/server/package.json ./tsp-output/server/ # Install build and dev dependencies RUN npm i --force --no-package-lock RUN cd tsp-output/server && npm install # Copy the rest of the application code COPY . . # Build the TypeScript code RUN cd tsp-output/server && tsc #--------------------------------------------------------------- # Stage 2: Runtime stage FROM node:20-alpine AS runtime # Set NODE_ENV to production for better performance ENV NODE_ENV=production WORKDIR /app # Copy only the server package files COPY tsp-output/server/package.json ./ # Install only production dependencies RUN npm install # Copy all necessary files from the builder stage # This includes the compiled JavaScript, any static assets, etc. COPY --from=builder /app/tsp-output/server/dist ./dist # Set default port and expose it ENV PORT=3000 EXPOSE 3000 # Run the application CMD ["node", "./dist/src/index.js"]
TypeSpec プロジェクトのルートで、
./infra
ディレクトリを作成します。./infra/main.bicepparam
ファイルを作成し、次のようにコピーして、デプロイに必要なパラメーターを定義します。using './main.bicep' param environmentName = readEnvironmentVariable('AZURE_ENV_NAME', 'dev') param ___location = readEnvironmentVariable('AZURE_LOCATION', 'eastus2') param deploymentUserPrincipalId = readEnvironmentVariable('AZURE_PRINCIPAL_ID', '')
このパラメーター リストには、このデプロイに必要な最小パラメーターが用意されています。
./infra/main.bicep
ファイルを作成し、次の内容をコピーして、プロビジョニングとデプロイ用の Azure リソースを定義します。metadata description = 'Bicep template for deploying a GitHub App using Azure Container Apps and Azure Container Registry.' targetScope = 'resourceGroup' param serviceName string = 'api' var databaseName = 'WidgetDb' var containerName = 'Widgets' var partitionKey = '/id' @minLength(1) @maxLength(64) @description('Name of the environment that can be used as part of naming resource convention') param environmentName string @minLength(1) @description('Primary ___location for all resources') param ___location string @description('Id of the principal to assign database and application roles.') param deploymentUserPrincipalId string = '' var resourceToken = toLower(uniqueString(resourceGroup().id, environmentName, ___location)) var tags = { 'azd-env-name': environmentName repo: 'https://github.com/typespec' } module managedIdentity 'br/public:avm/res/managed-identity/user-assigned-identity:0.4.1' = { name: 'user-assigned-identity' params: { name: 'identity-${resourceToken}' ___location: ___location tags: tags } } module cosmosDb 'br/public:avm/res/document-db/database-account:0.8.1' = { name: 'cosmos-db-account' params: { name: 'cosmos-db-nosql-${resourceToken}' ___location: ___location locations: [ { failoverPriority: 0 locationName: ___location isZoneRedundant: false } ] tags: tags disableKeyBasedMetadataWriteAccess: true disableLocalAuth: true networkRestrictions: { publicNetworkAccess: 'Enabled' ipRules: [] virtualNetworkRules: [] } capabilitiesToAdd: [ 'EnableServerless' ] sqlRoleDefinitions: [ { name: 'nosql-data-plane-contributor' dataAction: [ 'Microsoft.DocumentDB/databaseAccounts/readMetadata' 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*' 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/*' ] } ] sqlRoleAssignmentsPrincipalIds: union( [ managedIdentity.outputs.principalId ], !empty(deploymentUserPrincipalId) ? [deploymentUserPrincipalId] : [] ) sqlDatabases: [ { name: databaseName containers: [ { name: containerName paths: [ partitionKey ] } ] } ] } } module containerRegistry 'br/public:avm/res/container-registry/registry:0.5.1' = { name: 'container-registry' params: { name: 'containerreg${resourceToken}' ___location: ___location tags: tags acrAdminUserEnabled: false anonymousPullEnabled: true publicNetworkAccess: 'Enabled' acrSku: 'Standard' } } var containerRegistryRole = subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', '8311e382-0749-4cb8-b61a-304f252e45ec' ) module registryUserAssignment 'br/public:avm/ptn/authorization/resource-role-assignment:0.1.1' = if (!empty(deploymentUserPrincipalId)) { name: 'container-registry-role-assignment-push-user' params: { principalId: deploymentUserPrincipalId resourceId: containerRegistry.outputs.resourceId roleDefinitionId: containerRegistryRole } } module logAnalyticsWorkspace 'br/public:avm/res/operational-insights/workspace:0.7.0' = { name: 'log-analytics-workspace' params: { name: 'log-analytics-${resourceToken}' ___location: ___location tags: tags } } module containerAppsEnvironment 'br/public:avm/res/app/managed-environment:0.8.0' = { name: 'container-apps-env' params: { name: 'container-env-${resourceToken}' ___location: ___location tags: tags logAnalyticsWorkspaceResourceId: logAnalyticsWorkspace.outputs.resourceId zoneRedundant: false } } module containerAppsApp 'br/public:avm/res/app/container-app:0.9.0' = { name: 'container-apps-app' params: { name: 'container-app-${resourceToken}' environmentResourceId: containerAppsEnvironment.outputs.resourceId ___location: ___location tags: union(tags, { 'azd-service-name': serviceName }) ingressTargetPort: 3000 ingressExternal: true ingressTransport: 'auto' stickySessionsAffinity: 'sticky' scaleMaxReplicas: 1 scaleMinReplicas: 1 corsPolicy: { allowCredentials: true allowedOrigins: [ '*' ] } managedIdentities: { systemAssigned: false userAssignedResourceIds: [ managedIdentity.outputs.resourceId ] } secrets: { secureList: [ { name: 'azure-cosmos-db-nosql-endpoint' value: cosmosDb.outputs.endpoint } { name: 'user-assigned-managed-identity-client-id' value: managedIdentity.outputs.clientId } ] } containers: [ { image: 'mcr.microsoft.com/devcontainers/typescript-node' name: serviceName resources: { cpu: '0.25' memory: '.5Gi' } env: [ { name: 'AZURE_COSMOS_ENDPOINT' secretRef: 'azure-cosmos-db-nosql-endpoint' } { name: 'AZURE_CLIENT_ID' secretRef: 'user-assigned-managed-identity-client-id' } ] } ] } } output AZURE_COSMOS_ENDPOINT string = cosmosDb.outputs.endpoint output AZURE_COSMOS_DATABASE string = databaseName output AZURE_COSMOS_CONTAINER string = containerName output AZURE_COSMOS_PARTITION_KEY string = partitionKey output AZURE_CONTAINER_REGISTRY_ENDPOINT string = containerRegistry.outputs.loginServer output AZURE_CONTAINER_REGISTRY_NAME string = containerRegistry.outputs.name
OUTPUT 変数を使用すると、プロビジョニングされたクラウド リソースをローカル開発で使用できます。
Azure にアプリケーションをデプロイする
このアプリケーションは、Azure Container Apps を使用して Azure にデプロイできます。
プロジェクトのルートにあるターミナルで、Azure Developer CLI に対して認証を行います。
azd auth login
Azure Developer CLI を使用して Azure Container Apps にデプロイします。
azd up
次のプロンプトには提供された答えで回答してください。
- 一意の環境名を入力します。
tsp-server-js
- 使用する Azure サブスクリプションを選択する: サブスクリプションを選択する
- 使用する Azure の場所を選択する: 近くの場所を選択します
- 使用するリソース グループを選択する: [新しいリソース グループの作成] を選択します
- 新しいリソース グループの名前を入力し、提供されたデフォルトを受け入れてください。
- 一意の環境名を入力します。
デプロイが完了するまで待ちます。 応答には、次のような情報が含まれます。
Deploying services (azd deploy) (✓) Done: Deploying service api - Endpoint: https://container-app-123.ambitiouscliff-456.centralus.azurecontainerapps.io/ SUCCESS: Your up workflow to provision and deploy to Azure completed in 6 minutes 32 seconds.
ブラウザーでアプリケーションを使用する
デプロイ後、次のことができます。
- コンソールで、
Endpoint
URL を選択してブラウザーで開きます。 - Swagger UI を使用するには、エンドポイントにルート
/.api-docs
を追加します。 - API を使用してウィジェットを作成、読み取り、更新、削除するには、各メソッドで 今すぐ試 す機能を使用します。
アプリケーションを拡張する
エンドツーエンドのプロセス全体が機能したら、引き続き API をビルドします。
-
typeSpec 言語の詳細を確認し、
./main.tsp
に API と API レイヤーの機能を追加します。 -
エミッタを追加し、
./tspconfig.yaml
でそのパラメータを設定します。 - TypeSpec ファイルにさらに機能を追加する場合は、サーバー プロジェクトのソース コードでそれらの変更をサポートします。
- Azure ID で パスワードレス認証 を引き続き使用します。
リソースをクリーンアップする
このクイック スタートが完了したら、Azure リソースを削除できます。
azd down
または、Azure portal から直接リソース グループを削除します。
Next steps
- TypeSpec documentation
- Azure Cosmos DB ドキュメント
- Node.js アプリを Azure にデプロイする
- Azure Container Apps のドキュメント