Aracılığıyla paylaş


Hızlı Başlangıç: TypeSpec ve TypeScript ile yeni bir API projesi oluşturma

Bu hızlı başlangıçta: ReSTful TypeScript API uygulaması tasarlamak, oluşturmak ve uygulamak için TypeSpec kullanmayı öğrenin. TypeSpec, bulut hizmeti API'lerini açıklamaya yönelik bir açık kaynak dildir ve birden çok platform için istemci ve sunucu kodu oluşturur. Bu hızlı başlangıcı izleyerek, API sözleşmenizi bir kez tanımlayıp tutarlı uygulamalar oluşturarak daha sürdürülebilir ve iyi belgelenmiş API hizmetleri oluşturmanıza yardımcı olmayı öğreneceksiniz.

Bu hızlı başlangıç rehberinde şunları yapacaksınız:

  • TypeSpec kullanarak API'nizi tanımlama
  • API sunucusu uygulaması oluşturma
  • Azure Cosmos DB'yi kalıcı depolama için tümleştirme
  • Azure’a dağıtın
  • API'nizi çalıştırma ve test edin

Important

@typespec/http-server-js emitter şu anda ÖNİzLEME aşamasındadır. Bu bilgiler, yayımlanmadan önce önemli ölçüde değiştirilebilen yayın öncesi bir ürünle ilgilidir. Microsoft, burada sağlanan bilgilerle ilgili olarak açık veya zımni hiçbir garanti vermez.

Prerequisites

TypeSpec ile geliştirme

TypeSpec, API'nizi dilden bağımsız bir şekilde tanımlar ve birden çok platform için API sunucusunu ve istemci kitaplığını oluşturur. Bu işlev şunları yapmanızı sağlar:

  • API sözleşmenizi bir kez tanımlama
  • Tutarlı sunucu ve istemci kodu oluşturma
  • API altyapısı yerine iş mantığı uygulamaya odaklanma

TypeSpec, API hizmet yönetimi sağlar:

  • API tanım dili
  • API için sunucu tarafı yönlendirme ara yazılımı
  • API'yi tüketmek için istemci kitaplıkları

İstemci istekleri ve sunucu tümleştirmeleri sağlarsınız:

  • Veritabanları, depolama ve mesajlaşma için Azure hizmetleri gibi ara yazılımlarda iş mantığı uygulama
  • API'niz için barındırma sunucusu (yerel olarak veya Azure'da)
  • Yinelenebilir hazırlık ve dağıtım için dağıtım betikleri

Yeni bir TypeSpec uygulaması oluşturma

  1. API sunucusunu ve TypeSpec dosyalarını tutmak için yeni bir klasör oluşturun.

    mkdir my_typespec_quickstart
    cd my_typespec_quickstart
    
  2. TypeSpec derleyicisini genel olarak yükleyin:

    npm install -g @typespec/compiler
    
  3. TypeSpec'in doğru yüklendiğini denetleyin:

    tsp --version
    
  4. TypeSpec projesini başlatın:

    tsp init
    
  5. Sağlanan yanıtlarla aşağıdaki istemleri yanıtlayın:

    • Burada yeni bir proje başlatılasın mı? Y
    • Proje şablonu seçlsin mi? Genel REST API
    • Proje adı girin: Pencere öğeleri
    • Hangi yayıcıları kullanmak istiyorsunuz?
      • OpenAPI 3.1 belgesi
      • JavaScript sunucu saplamaları

    TypeSpec emitter'lar, TypeSpec derleme işlemini yansıtmak ve yapıtlar oluşturmak için çeşitli TypeSpec derleyici API'lerini kullanan kütüphanelerdir.

  6. Devam etmeden önce başlatma işleminin tamamlanmasını bekleyin.

  7. Projeyi derleyin:

    tsp compile .
    
  8. TypeSpec, içinde ./tsp-outputvarsayılan projeyi oluşturur ve iki ayrı klasör oluşturur:

    • şema , OpenApi 3 belirtimidir. içindeki ./main.tsp birkaç satırın sizin için 200'den fazla OpenApi belirtimi satırı oluşturduğuna dikkat edin.
    • sunucu , oluşturulan ara yazılımdır. Bu ara yazılım bir Node.js sunucu projesine eklenebilir.
      • ./tsp-output/js/src/generated/models/all/demo-service.ts Pencere Öğeleri API'sinin arabirimlerini tanımlar.
      • ./tsp-output/js/src/generated/http/openapi3.ts Open API belirtimini bir TypeScript dosyası olarak tanımlar ve TypeSpec projenizi her derlediğinizde yeniden oluşturulur.

TypeSpec vericilerini yapılandırma

TYPESpec dosyalarını kullanarak API sunucusu oluşturma işlemini tüm Express.js sunucunun iskelesini yapılandıracak şekilde yapılandırın.

  1. ./tsconfig.yaml öğesini açın ve mevcut yapılandırmayı aşağıdaki YAML ile değiştirin:

    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
    

    Bu yapılandırma eksiksiz bir Express.js API sunucusu oluşturur:

    • express: Swagger kullanıcı arabirimi dahil olmak üzere Express.js API sunucusunu oluşturun.
    • emitter-output-dir: ./server dizinine her şeyi oluşturun.
  2. Var olan ./tsp-outputöğesini silin. Endişelenmeyin, bir sonraki adımda sunucuyu oluşturacaksınız.

  3. Express.js sunucusu oluşturmak için TypeSpec JavaScript vericisini kullanın:

    npx hsjs-scaffold
    
  4. Yeni ./tsp-output/server dizine geçin:

    cd ./tsp-output/server
    
  5. TypeScript'i JavaScript'e derleyin.

    tsc
    
  6. Projeyi çalıştırın:

    npm start
    

    Bildirimin Tarayıcıda açılmasını bekleyin.

  7. Tarayıcıyı açın ve adresine http://localhost:3000/.api-docsgidin.

    Pencere Öğeleri API'sine yönelik Swagger kullanıcı arabirimini görüntüleyen tarayıcının ekran görüntüsü.

  8. Varsayılan TypeSpec API'sinin ve sunucunun her ikisi de çalışır. Bu API sunucusunu tamamlamak istiyorsanız içindeki Pencere Öğeleri API'lerini ./tsp-output/server/src/controllers/widgets.tsdesteklemek için iş mantığınızı ekleyin. Kullanıcı arabirimi, sabit kodlanmış sahte veriler döndüren API'ye bağlanır.

Uygulama dosyası yapısını anlama

konumunda tsp-output/server/ bulunan Express.js proje yapısı, oluşturulan sunucuyu, package.jsonve Azure tümleştirmeniz için ara yazılımı içerir.

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

Üst TypeSpec projesinin dosya yapısı içinde bu Express.js projesini tsp-outputiçerir:

├── tsp-output
├── .gitignore
├── main.tsp
├── package-lock.json
├── package.json
├── tspconfig.yaml

Kalıcılığı Azure Cosmos DB'nin no-sql veri tabanına değiştirin

Temel Express.js API sunucusu çalıştığına göre, Express.js sunucusunu kalıcı bir veri deposu için Azure Cosmos DB ile çalışacak şekilde güncelleştirin. Bu, index.ts üzerindeki değişikliklerin ara yazılımda Cosmos DB tümleştirmesini kullanmasını içerir. Tüm değişiklikler dizinin dışında ./tsp-output/server/src/generated gerçekleşmelidir.

  1. Dizininde ./tsp-output/server Azure Cosmos DB'yi projeye ekleyin:

    npm install @azure/cosmos
    
  2. Azure'dakimlik doğrulaması yapmak için Azure Kimlik kitaplığını ekleyin:

    npm install @azure/identity
    
  3. Azure'a özgü kaynak kodu tutmak için bir ./tsp-output/server/src/azure dizin oluşturun.

  4. cosmosClient.ts Cosmos DB istemci nesnesi oluşturmak için bu dizinde dosyayı oluşturun ve aşağıdaki kodu yapıştırın:

    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'}`
      };
    };
    

    Dosyada uç nokta, veritabanı ve kapsayıcı kullanıldığına dikkat edin. Azure Kimlik kimlik bilgilerini DefaultAzureCredentialkullandığından bağlantı dizesine veya anahtara ihtiyacı yoktur. Hem yerel hem de üretim ortamları için bu güvenli kimlik doğrulama yöntemi hakkında daha fazla bilgi edinin.

  5. Yeni bir Pencere Öğesi denetleyicisi ./tsp-output/server/src/controllers/WidgetsCosmos.tsoluşturun ve Azure Cosmos DB için aşağıdaki tümleştirme kodunu yapıştırın.

    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;
      }
    }
    
  6. ./tsp-output/server/src/index.ts öğesini yeni denetleyiciyi içeri aktarmak için güncelleyin, Azure Cosmos DB ortam ayarlarını yapılandırın, ardından WidgetsCosmosController'ı oluşturup bunu yönlendiriciye iletin.

    // 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}`,
      );
    });
    
  7. konumundaki bir terminalde ./tsp-output/serverTypeScript'i JavaScript olarak derleyin.

    tsc
    

    Proje artık Cosmos DB entegrasyonu ile çalışıyor. Şimdi Azure kaynaklarını oluşturmak ve projeyi dağıtmak için dağıtım betiklerini oluşturalım.

Dağıtım altyapısı oluşturma

Azure Geliştirici CLI ve Bicep şablonlarıyla tekrarlanabilir bir dağıtıma sahip olmak için gereken dosyaları oluşturun.

  1. TypeSpec projesinin kökünde bir azure.yaml dağıtım tanımı dosyası oluşturun ve aşağıdaki kaynağa yapıştırın:

    # 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
    

    Bu yapılandırmanın TypeSpec projesinin tamamına başvurduğunu fark edin.

  2. TypeSpec projesinin kökünde, Azure Container Apps kapsayıcısını oluşturmak için kullanılan ./Dockerfile öğesini oluşturun.

    # 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"]
    
  3. TypeSpec projesinin kökünde bir ./infra dizin oluşturun.

  4. ./infra/main.bicepparam Dağıtım için ihtiyacımız olan parametreleri tanımlamak için aşağıdaki dosyayı oluşturun ve kopyalayın:

    using './main.bicep'
    
    param environmentName = readEnvironmentVariable('AZURE_ENV_NAME', 'dev')
    param location = readEnvironmentVariable('AZURE_LOCATION', 'eastus2')
    param deploymentUserPrincipalId = readEnvironmentVariable('AZURE_PRINCIPAL_ID', '')
    

    Bu param listesi, bu dağıtım için gereken en düşük parametreleri sağlar.

  5. ./infra/main.bicep Sağlama ve dağıtım için Azure kaynaklarını tanımlamak üzere aşağıdaki dosya ve kopyayı oluşturun:

    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 değişkenleri, sağlanan bulut kaynaklarını yerel geliştirmenizle birlikte kullanmanıza olanak sağlar.

Uygulamayı Azure'a dağıtma

Bu uygulamayı Azure Container Apps kullanarak Azure'a dağıtabilirsiniz:

  1. Projenin kökündeki bir terminalde Azure Geliştirici CLI'sında kimlik doğrulaması:

    azd auth login
    
  2. Azure Geliştirici CLI'sini kullanarak Azure Container Apps'e dağıtma:

    azd up
    
  3. Sağlanan yanıtlarla aşağıdaki istemleri yanıtlayın:

    • Benzersiz bir ortam adı girin: tsp-server-js
    • Kullanılacak Azure Aboneliğini seçin: Aboneliğinizi seçin
    • Kullanılacak bir Azure konumu seçin: Size yakın bir konum seçin
    • Kullanılacak kaynak grubunu seçin: Yeni kaynak grubu oluştur'u seçin
    • Yeni kaynak grubu için bir ad girin: sağlanan varsayılanı kabul edin
  4. Dağıtım tamamlanana kadar bekleyin. Yanıt aşağıdakine benzer bilgiler içerir:

    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.
    

Uygulamayı tarayıcıda kullanma

Dağıtıldıktan sonra şunları yapabilirsiniz:

  1. Konsolda Endpoint URL'yi seçin ve bir tarayıcıda açın.
  2. Swagger kullanıcı arabirimini kullanmak için uç noktaya /.api-docs yolunu ekleyin.
  3. API aracılığıyla pencere öğeleri oluşturmak, okumak, güncelleştirmek ve silmek için her yöntemde Şimdi deneyin özelliğini kullanın.

Uygulamanızı büyütme

Artık uçtan uca sürecin tamamı çalıştığına göre API'nizi oluşturmaya devam edin:

  • içinde daha fazla API ve API katmanı özelliği eklemek için ./main.tsp hakkında daha fazla bilgi edinin.
  • Daha fazla emitter ekleyin ve parametrelerini ./tspconfig.yaml yapılandırın.
  • TypeSpec dosyalarınıza daha fazla özellik ekledikçe, bu değişiklikleri sunucu projesindeki kaynak koduyla destekleyin.
  • Azure Identity ile parolasız kimlik doğrulamasını kullanmaya devam edin.

Kaynakları temizle

Bu hızlı başlangıcı tamamladığınızda Azure kaynaklarını kaldırabilirsiniz:

azd down

Veya kaynak grubunu doğrudan Azure portalından silin.

Sonraki Adımlar