Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Questa guida introduttiva illustra come usare TypeSpec per progettare, generare e implementare un'applicazione API TypeScript RESTful. TypeSpec è un linguaggio open source per descrivere le API del servizio cloud e genera codice client e server per più piattaforme. Seguendo questa guida introduttiva si apprenderà come definire il contratto API una sola volta e generare implementazioni coerenti, consentendo di creare servizi API più gestibili e ben documentati.
Questa guida introduttiva spiega come:
- Definire l'API usando TypeSpec
- Creare un'applicazione server API
- Integrare Azure Cosmos DB per l'archiviazione permanente
- Distribuzione su Azure
- Eseguire e testare l'API
Important
@typespec/http-server-js l'emettitore è attualmente in ANTEPRIMA.
Queste informazioni si riferiscono a un prodotto in versione preliminare che può essere modificato in modo sostanziale prima del rilascio. Microsoft non fornisce alcuna garanzia, espressa o implicita, in relazione alle informazioni fornite qui.
Prerequisites
- Un account Azure attivo. Crea gratuitamente un account se non ne hai uno.
- Node.js LTS installato nel sistema.
- TypeScript per la scrittura e la compilazione di codice TypeScript.
- Docker
- Visual Studio Code
- Estensione TypeSpec
- Facoltativo: distribuzione con l'interfaccia della riga di comando per sviluppatori di Azure
Sviluppo con TypeSpec
TypeSpec definisce l'API in modo indipendente dal linguaggio e genera il server API e la libreria client per più piattaforme. Questa funzionalità consente di:
- Definire il contratto API una sola volta
- Generare codice client e server coerenti
- Concentrarsi sull'implementazione della logica di business anziché sull'infrastruttura API
TypeSpec fornisce la gestione dei servizi API:
- Linguaggio di definizione API
- Middleware di routing lato server per l'API
- Librerie client per l'utilizzo dell'API
Tu fornisci richieste client e integrazioni server:
- Implementare la logica di business nel middleware, ad esempio i servizi di Azure per database, archiviazione e messaggistica
- Server di hosting per l'API (in locale o in Azure)
- Script di implementazione per il provisioning e l'implementazione ripetibili
Creare una nuova applicazione TypeSpec
Creare una nuova cartella per contenere il server API e i file TypeSpec.
mkdir my_typespec_quickstart cd my_typespec_quickstartInstallare il compilatore TypeSpec a livello globale:
npm install -g @typespec/compilerControllare TypeSpec installato correttamente:
tsp --versionInizializzare il progetto TypeSpec:
tsp initRispondere alle istruzioni seguenti con le risposte fornite:
- Inizializzare un nuovo progetto qui? Y
- Selezionare un modello di progetto? API REST generica
- Immettere un nome di progetto: Widget
- Quali emettitori vuoi usare?
- Documento OpenAPI 3.1
- Stub del server JavaScript
Gli emettitori TypeSpec sono librerie che utilizzano varie API del compilatore di TypeSpec per interagire con il processo di compilazione di TypeSpec e generare artefatti.
Attendere il completamento dell'inizializzazione prima di continuare.
Compilare il progetto:
tsp compile .TypeSpec genera il progetto predefinito in
./tsp-output, creando due cartelle separate:-
schema è la specifica OpenApi 3. Notate che le poche righe in
./main.tsphanno generato per voi oltre 200 righe della specifica OpenApi. -
server è il middleware generato. Questo middleware può essere incorporato in un progetto server Node.js.
-
./tsp-output/js/src/generated/models/all/demo-service.tsdefinisce le interfacce per l'API Widget. -
./tsp-output/js/src/generated/http/openapi3.tsdefinisce la specifica open API come file TypeScript e viene rigenerata ogni volta che si compila il progetto TypeSpec.
-
-
schema è la specifica OpenApi 3. Notate che le poche righe in
Configurare gli emettitori TypeSpec
Usare i file TypeSpec per configurare la generazione del server API e creare la struttura dell'intero server Express.js.
./tsconfig.yamlAprire e sostituire la configurazione esistente con il codice YAML seguente: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: trueQuesta configurazione crea un server API completo Express.js:
-
express: genera il server API Express.js, inclusa l'interfaccia utente di Swagger. -
emitter-output-dir: genera tutto nella./serverdirectory.
-
Eliminare l'esistente
./tsp-output. Non preoccuparti, il server verrà generato nel passaggio successivo.Usare l'emettitore JavaScript TypeSpec per creare il server di Express.js:
npx hsjs-scaffoldPassare alla nuova
./tsp-output/serverdirectory:cd ./tsp-output/serverCompila TypeScript in JavaScript.
tscEseguire il progetto:
npm startAttendere che la notifica venga aperta nel browser.
Aprire il browser e passare a
http://localhost:3000/.api-docs.
L'API TypeSpec predefinita e il server funzionano entrambi. Se vuoi completare questo server API, aggiungi la logica aziendale per supportare le API Widget in
./tsp-output/server/src/controllers/widgets.ts. L'interfaccia utente è connessa all'API che restituisce dati fittizi predefiniti.
Informazioni sulla struttura dei file dell'applicazione
La struttura del progetto Express.js disponibile in tsp-output/server/ include il server generato, il package.jsone il middleware per l'integrazione di 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
La struttura dei file per il progetto padre TypeSpec include questo progetto Express.js in tsp-output:
├── tsp-output
├── .gitignore
├── main.tsp
├── package-lock.json
├── package.json
├── tspconfig.yaml
Modifica la persistenza in Azure Cosmos DB NoSQL
Ora che il server API di base Express.js funziona, aggiornare il server Express.js per l'uso con Azure Cosmos DB per un archivio dati permanente. Sono incluse le modifiche apportate a index.ts per usare l'integrazione di Cosmos DB nel middleware. Tutte le modifiche devono essere apportate all'esterno della ./tsp-output/server/src/generated directory.
./tsp-output/serverNella directory aggiungere Azure Cosmos DB al progetto:npm install @azure/cosmosAggiungere la libreria di identità di Azure per l'autenticazione in Azure:
npm install @azure/identityCreare una
./tsp-output/server/src/azuredirectory per contenere il codice sorgente specifico di Azure.Creare il
cosmosClient.tsfile in tale directory per creare un oggetto client Cosmos DB e incollarlo nel codice seguente: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'}` }; };Si noti che il file usa l'endpoint, il database e il contenitore. Non è necessaria una stringa di connessione o una chiave perché usa le credenziali
DefaultAzureCredentialdi Identità di Azure. Altre informazioni su questo metodo di autenticazione sicura per ambienti locali e di produzione .Creare un nuovo controller widget,
./tsp-output/server/src/controllers/WidgetsCosmos.tse incollare il codice di integrazione seguente per 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; } }Aggiorna il
./tsp-output/server/src/index.tsper importare il nuovo controller, ottenere le impostazioni dell'ambiente Azure Cosmos DB, quindi crea il WidgetsCosmosController e passalo al router.// 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}`, ); });In un terminale in
./tsp-output/servercompilare TypeScript in JavaScript.tscOra il progetto viene sviluppato con l'integrazione di Cosmos DB. Creare gli script di distribuzione per creare le risorse di Azure e distribuire il progetto.
Creare un'infrastruttura di distribuzione
Creare i file necessari per una distribuzione ripetibile con Azure Developer CLI e i modelli Bicep.
Alla radice del progetto TypeSpec, crea un
azure.yamlfile di definizione della distribuzione e incolla il seguente contenuto:# 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: trueSi noti che questa configurazione fa riferimento all'intero progetto TypeSpec.
Alla radice del progetto TypeSpec, creare il
./Dockerfileche viene utilizzato per costruire il contenitore per 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"]Nella radice del progetto TypeSpec creare una
./infradirectory.Creare un
./infra/main.bicepparamfile e copiarlo nel modo seguente per definire i parametri necessari per la distribuzione:using './main.bicep' param environmentName = readEnvironmentVariable('AZURE_ENV_NAME', 'dev') param location = readEnvironmentVariable('AZURE_LOCATION', 'eastus2') param deploymentUserPrincipalId = readEnvironmentVariable('AZURE_PRINCIPAL_ID', '')Questo elenco di parametri fornisce i parametri minimi necessari per questa distribuzione.
Creare un
./infra/main.bicepfile e copiarlo nel modo seguente per definire le risorse di Azure per il provisioning e la distribuzione: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.nameLe variabili OUTPUT consentono di usare le risorse cloud di cui è stato effettuato il provisioning con lo sviluppo locale.
Distribuire l'applicazione in Azure
È possibile distribuire questa applicazione in Azure usando App Azure Container:
In un terminale nella radice del progetto eseguire l'autenticazione all'interfaccia della riga di comando per sviluppatori di Azure:
azd auth loginDistribuire su Azure Container Apps usando la CLI per sviluppatori di Azure.
azd upRispondere alle istruzioni seguenti con le risposte fornite:
- Immettere un nome di ambiente univoco:
tsp-server-js - Selezionare una sottoscrizione di Azure da usare: selezionare la sottoscrizione
- Selezionare una località di Azure da usare: selezionare una località nelle vicinanze
- Selezionare un gruppo di risorse da usare: selezionare Crea un nuovo gruppo di risorse
- Immettere un nome per il nuovo gruppo di risorse: accettare l'impostazione predefinita specificata
- Immettere un nome di ambiente univoco:
Attendere il completamento della distribuzione. La risposta include informazioni simili alle seguenti:
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.
Usare l'applicazione nel browser
Dopo la distribuzione, puoi:
- Nella console selezionare l'URL
Endpointper aprirlo in un browser. - Aggiungere la route,
/.api-docs, all'endpoint per usare Swagger UI. - Usare la funzionalità Prova ora in ogni metodo per creare, leggere, aggiornare ed eliminare widget tramite l'API.
Espandere l'applicazione
Ora che l'intero processo end-to-end funziona, continuare a compilare l'API:
- Scopri di più sul linguaggio TypeSpec per aggiungere ulteriori API e funzionalità del livello API in
./main.tsp. - Aggiungere altri emettitori e configurare i relativi parametri in
./tspconfig.yaml. - Man mano che si aggiungono altre funzionalità nei file TypeSpec, supportare tali modifiche con il codice sorgente nel progetto server.
- Continuare a usare l'autenticazione senza password con Identità di Azure.
Pulire le risorse
Al termine di questa guida introduttiva, è possibile rimuovere le risorse di Azure:
azd down
In alternativa, eliminare il gruppo di risorse direttamente dal portale di Azure.