이 빠른 시작에서는 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 Code
- TypeSpec 확장
- 선택 사항: Azure Developer 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_quickstartTypeSpec 컴파일러를 전역적으로 설치합니다.
npm install -g @typespec/compilerTypeSpec이 올바르게 설치되었는지 확인합니다.
tsp --versionTypeSpec 프로젝트를 초기화합니다.
tsp init제공된 답변으로 다음 프롬프트에 응답합니다.
- 여기에서 새 프로젝트를 초기화하시겠습니까? Y
- 프로젝트 템플릿을 선택하시겠습니까? 일반 REST API
- 프로젝트 이름 입력: 위젯
- 어떤 내보내기를 사용하시겠습니까?
- OpenAPI 3.1 문서
- JavaScript 서버 스텁
TypeSpec 발생기는 다양한 TypeSpec 컴파일러 API를 활용하여 TypeSpec 컴파일 프로세스를 고찰하고 아티팩트를 생성하는 라이브러리입니다.
계속하기 전에 초기화가 완료되기를 기다립니다.
프로젝트를 컴파일합니다.
tsp compile .TypeSpec은 두 개의 개별 폴더를 만드는 기본
./tsp-output프로젝트를 생성합니다.-
스키마 는 OpenApi 3 사양입니다.
./main.tsp의 몇 줄이 여러분을 위해 200줄이 넘는 OpenApi 사양을 생성한 것을 주목하세요. -
서버 는 생성된 미들웨어입니다. 이 미들웨어는 Node.js 서버 프로젝트에 통합할 수 있습니다.
-
./tsp-output/js/src/generated/models/all/demo-service.ts는 위젯 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/serverTypeScript를 JavaScript로 컴파일합니다.
tsc프로젝트를 실행합니다.
npm start브라우저에서 알림이 열릴 때까지 기다립니다.
브라우저를 열고
http://localhost:3000/.api-docs웹 사이트로 이동합니다.
기본 TypeSpec API와 서버가 모두 작동합니다. 이 API 서버를 완성하려면 비즈니스 논리를 추가하여 위젯 API
./tsp-output/server/src/controllers/widgets.ts를 지원해야 합니다. UI는 하드 코딩된 가짜 데이터를 반환하는 API에 연결됩니다.
애플리케이션 파일 구조 이해
Express.js 프로젝트 구조 tsp-output/server/ 에는 생성된 서버, 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 서버를 업데이트합니다. 여기에는 미들웨어에서 index.ts Cosmos DB 통합을 사용하도록 변경된 내용이 포함됩니다. 모든 변경 내용은 ./tsp-output/server/src/generated 디렉터리 외부에서 일어나야 합니다.
./tsp-output/server디렉터리에서 프로젝트에 Azure Cosmos DB를 추가합니다.npm install @azure/cosmosAzure ID 라이브러리를 추가하여 Azure에 인증합니다.
npm install @azure/identityAzure와
./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.nameOUTPUT 변수를 사용하면 로컬 개발과 함께 프로비전된 클라우드 리소스를 사용할 수 있습니다.
Azure에 애플리케이션 배포
Azure Container Apps를 사용하여 Azure에 이 애플리케이션을 배포할 수 있습니다.
프로젝트의 루트에 있는 터미널에서 Azure 개발자 CLI에 인증합니다.
azd auth loginAzure 개발자 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.
브라우저에서 애플리케이션 사용
배포되면 다음을 수행할 수 있습니다.
- 콘솔에서 URL을
Endpoint선택하여 브라우저에서 엽니다. - Swagger UI를 사용하기 위해 엔드포인트에 경로를
/.api-docs추가합니다. - 이제 각 메서드에서 Try it 기능을 사용하여 API를 통해 위젯을 만들고, 읽고, 업데이트하고, 삭제합니다.
애플리케이션 확장
이제 전체 엔드-엔드 프로세스가 작동했으므로 API를 계속 빌드합니다.
- 에 API 및 API 계층 기능을 더 추가하려면 TypeSpec 언어 에 대해 자세히 알아봅니다
./main.tsp. -
방출기를 더 추가하고 해당 매개변수를
./tspconfig.yaml에서 구성합니다. - TypeSpec 파일에 추가 기능을 추가하면 서버 프로젝트의 소스 코드를 사용하여 이러한 변경 내용을 지원합니다.
- Azure ID에서 암호 없는 인증 을 계속 사용합니다.
자원을 정리하세요
이 빠른 시작을 완료하면 Azure 리소스를 제거할 수 있습니다.
azd down
또는 Azure Portal에서 직접 리소스 그룹을 삭제합니다.
다음 단계
- TypeSpec 설명서
- Azure Cosmos DB 설명서
- Azure에 Node.js 앱 배포
- Azure Container Apps 설명서