在本快速入門中:瞭解如何使用 TypeSpec 來設計、產生及實作 RESTful TypeScript API 應用程式。 TypeSpec 是一種開放原始碼語言,用於描述雲端服務 API,並針對多個平台產生客戶端和伺服器程序代碼。 遵循本快速入門,您將瞭解如何定義 API 合約一次,併產生一致的實作,協助您建置更多可維護且記錄良好的 API 服務。
在本快速入門中,您將:
- 使用 TypeSpec 定義您的 API
- 建立 API 伺服器應用程式
- 整合 Azure Cosmos DB 以實現持久性儲存
- 部署至 Azure
- 執行及測試您的 API
Important
@typespec/http-server-js 發出器目前處於預覽狀態。
這項資訊涉及發行前產品,在發行之前可能會有大幅修改。 Microsoft針對此處提供的資訊,不提供任何明示或默示擔保。
Prerequisites
- 作用中的 Azure 帳戶。 如果您沒有帳戶,請免費建立帳戶。
- Node.js LTS 安裝在你的系統上。
- 用於撰寫和編譯 TypeScript 程式代碼的 TypeScript。
- Docker
- Visual Studio 程式碼
- TypeSpec 擴充功能
- 選擇性:使用 Azure 開發人員 CLI 進行部署
使用 TypeSpec 進行開發
TypeSpec 會以語言無關的方式定義您的 API,並針對多個平台產生 API 伺服器和用戶端連結庫。 此功能讓您可以:
- 定義 API 合約只需一次即可
- 產生一致的伺服器和用戶端程序代碼
- 專注於實作商業規則,而不是 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產生預設專案,並建立兩個不同的資料夾:-
schema 是 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 專案時重新產生。
-
-
schema 是 OpenApi 3 規格。 請注意,為您產生的幾行
設定 TypeSpec 發射器
使用 TypeSpec 檔案來設定 API 伺服器產生,以建立整個 Express.js 伺服器。
開啟
./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:產生 Express.js API 伺服器,包括 Swagger UI。 -
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 伺服器,請新增商業邏輯以支援 Widgets API 中的
./tsp-output/server/src/controllers/widgets.ts。 UI 會連線到 API,此 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 專案的檔案結構包括位於 tsp-output 中的這個 Express.js 專案:
├── tsp-output
├── .gitignore
├── main.tsp
├── package-lock.json
├── package.json
├── tspconfig.yaml
切換至 Azure Cosmos DB NoSQL 來實現資料持久性
基本的 Express.js API 伺服器現已運作,現在請更新 Express.js 伺服器,以將 Azure Cosmos DB 設為持久性資料存放庫。 這包括變更index.ts,以便在中間件中使用 Cosmos DB 整合。 所有變更都應該發生在目錄外部 ./tsp-output/server/src/generated 。
在
./tsp-output/server目錄中,將 Azure Cosmos DB 新增至專案:npm install @azure/cosmos新增 Azure 身分識別連結庫 以 向 Azure 進行驗證:
npm install @azure/identity建立
./tsp-output/server/src/azure目錄來保存 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 身分識別認證
DefaultAzureCredential。 深入瞭解這種用於本機和生產環境的安全驗證方法。建立新的 Widget 控制器,
./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 開發人員 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 專案的根目錄中,建立
./Dockerfile,用來建置 Azure Container Apps 的容器。# 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.nameOUTPUT 變數可讓您在本機開發時使用已布建的雲端資源。
將應用程式部署至 Azure
您可以使用 Azure Container Apps 將此應用程式部署至 Azure:
在專案根目錄的終端機中,向 Azure 開發人員 CLI 進行驗證:
azd auth login使用 Azure 開發人員 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.
在瀏覽器中使用應用程式
部署之後,您可以:
- 在控制台中,選取
EndpointURL以在瀏覽器中開啟它。 - 將路由
/.api-docs新增至端點,以使用 Swagger UI。 - 使用每個方法上的 [立即試用] 功能,透過 API 建立、讀取、更新和刪除小工具。
拓展您的應用程式
既然您已讓整個端對端程式正常運作,請繼續建置您的 API:
- 深入瞭解 TypeSpec 語言 ,以在 中
./main.tsp新增更多 API 和 API 層功能。 - 在中新增更多
./tspconfig.yaml並配置其參數。 - 當您在 TypeSpec 檔案中新增更多功能時,請在伺服器專案中使用原始程式碼支援這些變更。
- 繼續使用 Azure 身分識別的無密碼驗證。
清理資源
完成本快速入門后,您可以移除 Azure 資源:
azd down
或者直接從 Azure 入口網站刪除資源群組。