Versioning
Introduction
Section titled “Introduction”In this section, we’ll focus on implementing versioning in your REST API. Versioning allows you to manage changes to your API over time without breaking existing clients. We’ll introduce the @versioned
decorator, show how to define versions with enums, and demonstrate how to use the @added
decorator to specify version-specific models and operations.
Adding the Versioning Library
Section titled “Adding the Versioning Library”Before we can use the versioning decorators, we need to add the @typespec/versioning
library to our project. This involves updating the package.json
file and installing the necessary dependencies.
Step 1: Update package.json
Section titled “Step 1: Update package.json”Add the @typespec/versioning
library to your package.json
file, in both the peerDependencies
and devDependencies
sections. Your updated package.json
should look like this:
{ "name": "typespec-petstore", "version": "0.1.0", "type": "module", "peerDependencies": { "@typespec/compiler": "latest", "@typespec/http": "latest", "@typespec/openapi3": "latest", // highlight-next-line "@typespec/versioning": "latest" }, "devDependencies": { "@typespec/compiler": "latest", "@typespec/http": "latest", "@typespec/openapi3": "latest", // highlight-next-line "@typespec/versioning": "latest" }, "private": true}
Step 2: Install Dependencies
Section titled “Step 2: Install Dependencies”Run the following command to install the new dependencies:
tsp install
Introduction to the @versioned
Decorator
Section titled “Introduction to the @versioned Decorator”The @versioned
decorator is used to define different versions of your API. This decorator allows you to specify the versions that your API supports and manage changes across these versions.
Example: Defining API Versions
Section titled “Example: Defining API Versions”Let’s define two versions of our API, v1
and v2
:
import "@typespec/http";// highlight-next-lineimport "@typespec/versioning";
using Http;// highlight-next-lineusing Versioning;
@service(#{ title: "Pet Store" })@server("https://example.com", "Single server endpoint")// highlight-next-line@versioned(Versions)namespace PetStore;
// highlight-startenum Versions { v1: "1.0", v2: "2.0",}// highlight-end
In this example:
- We’re importing and using a new module,
@typespec/versioning
, which provides versioning support. - The
@versioned
decorator is used to define the versions supported by the API, defined in theVersions
enum. - The
Versions
enum specifies two versions:v1
(1.0) andv2
(2.0).
Generating OpenAPI Specifications for Different Versions
Section titled “Generating OpenAPI Specifications for Different Versions”Once versions are added, the TypeSpec compiler generates individual OpenAPI specifications for each version.
- main.tsp
- tspconfig.yaml
- package.json
Directorynode_modules/
- …
Directorytsp-output/
Directory@typespec/
Directoryopenapi3
- openapi.1.0.yaml
- openapi.2.0.yaml
Generating separate specs for each version ensures backward compatibility, provides clear documentation for developers to understand differences between versions, and simplifies maintenance by allowing independent updates to each version’s specifications.
By encapsulating different versions of the API within the context of the same TypeSpec project, we can manage all versions in a unified manner. This approach makes it easier to maintain consistency, apply updates, and ensure that all versions are properly documented and aligned with the overall API strategy.
Using the @added
Decorator
Section titled “Using the @added Decorator”The @added
decorator is used to indicate that a model or operation was added in a specific version of the API. This allows you to manage changes and additions to your API over time.
Example: Adding a New Model in a Specific Version
Section titled “Example: Adding a New Model in a Specific Version”Let’s add a Toy
model that is only available in version 2 of the API:
import "@typespec/http";import "@typespec/versioning";
using Http;using Versioning;
@service(#{ title: "Pet Store" })@server("https://example.com", "Single server endpoint")@versioned(Versions)namespace PetStore;
enum Versions { v1: "1.0", v2: "2.0",}
model Pet { id: int32;
@minLength(1) name: string;
@minValue(0) @maxValue(100) age: int32;
kind: petType;}
enum petType { dog: "dog", cat: "cat", fish: "fish", bird: "bird", reptile: "reptile",}
// highlight-start@added(Versions.v2)model Toy { id: int32; name: string;}// highlight-end
In this example:
- The
Toy
model is defined with the@added(Versions.v2)
decorator to indicate that it was added in version 2 of the API.
Version-Specific Operations
Section titled “Version-Specific Operations”Let’s define version-specific operations to manage toys for pets. These operations will only be available in version 2 of the API.
Example: Adding Version-Specific Operations
Section titled “Example: Adding Version-Specific Operations”import "@typespec/http";import "@typespec/versioning";
using Http;using Versioning;
@service(#{ title: "Pet Store" })@server("https://example.com", "Single server endpoint")@versioned(Versions)namespace PetStore;
enum Versions { v1: "1.0", v2: "2.0",}
model Pet { id: int32;
@minLength(1) name: string;
@minValue(0) @maxValue(100) age: int32;
kind: petType;}
enum petType { dog: "dog", cat: "cat", fish: "fish", bird: "bird", reptile: "reptile",}
@added(Versions.v2)model Toy { id: int32; name: string;}
model CommonParameters { @header requestID: string;
@query locale?: string;
@header clientVersion?: string;}
@route("/pets")namespace Pets { @get op listPets(...CommonParameters): { @statusCode statusCode: 200; @body pets: Pet[]; };
@get op getPet(@path petId: int32, ...CommonParameters): { @statusCode statusCode: 200; @body pet: Pet; } | { @statusCode statusCode: 404; @body error: NotFoundError; };
@post @useAuth(BearerAuth) op createPet(@body pet: Pet, ...CommonParameters): | { @statusCode statusCode: 201; @body newPet: Pet; } | { @statusCode statusCode: 202; @body acceptedPet: Pet; } | { @statusCode statusCode: 400; @body error: ValidationError; } | { @statusCode statusCode: 401; @body error: UnauthorizedError; };
@put @useAuth(BearerAuth) op updatePet(@path petId: int32, @body pet: Pet, ...CommonParameters): | { @statusCode statusCode: 200; @body updatedPet: Pet; } | { @statusCode statusCode: 400; @body error: ValidationError; } | { @statusCode statusCode: 401; @body error: UnauthorizedError; } | { @statusCode statusCode: 404; @body error: NotFoundError; } | { @statusCode statusCode: 500; @body error: InternalServerError; };
@delete @useAuth(BearerAuth) op deletePet(@path petId: int32, ...CommonParameters): { @statusCode statusCode: 204; } | { @statusCode statusCode: 401; @body error: UnauthorizedError; };
// highlight-start @route("{petId}/toys") namespace Toys { @added(Versions.v2) @get op listToys(@path petId: int32, ...CommonParameters): { @statusCode statusCode: 200; @body toys: Toy[]; } | { @statusCode statusCode: 404; @body error: NotFoundError; };
@added(Versions.v2) @post @useAuth(BearerAuth) op createToy(@path petId: int32, @body toy: Toy, ...CommonParameters): { @statusCode statusCode: 201; @body newToy: Toy; } | { @statusCode statusCode: 400; @body error: ValidationError; } | { @statusCode statusCode: 401; @body error: UnauthorizedError; };
@added(Versions.v2) @put @useAuth(BearerAuth) op updateToy(@path petId: int32, @path toyId: int32, @body toy: Toy, ...CommonParameters): | { @body updatedToy: Toy; } | { @statusCode statusCode: 400; @body error: ValidationError; } | { @statusCode statusCode: 401; @body error: UnauthorizedError; } | { @statusCode statusCode: 404; @body error: NotFoundError; };
@added(Versions.v2) @delete @useAuth(BearerAuth) op deleteToy(@path petId: int32, @path toyId: int32, ...CommonParameters): { @statusCode statusCode: 204; } | { @statusCode statusCode: 401; @body error: UnauthorizedError; }; } // highlight-end}
@errormodel NotFoundError { code: "NOT_FOUND"; message: string;}
@errormodel ValidationError { code: "VALIDATION_ERROR"; message: string; details: string[];}
@errormodel UnauthorizedError { code: "UNAUTHORIZED"; message: string;}
@errormodel InternalServerError { code: "INTERNAL_SERVER_ERROR"; message: string;}
model InternalServerErrorResponse { @statusCode statusCode: 500; @body error: InternalServerError;}
In this example:
- The
Toys
namespace is defined under thePets
namespace. - The
@added(Versions.v2)
decorator is applied to the operations within theToys
namespace to indicate that they were added in version 2 of the API. - The
Toys
namespace includes operations to list, create, update, and delete toys for a specific pet. These operations are only available in version 2 of the API.
Conclusion
Section titled “Conclusion”In the next section, we’ll dive into creating custom response models for your REST API.