Skip to content

Commit

Permalink
REST tutorial revisions (#4104)
Browse files Browse the repository at this point in the history
Revisions made based on test drive feedback from members of the PM team.
  • Loading branch information
mario-guerra authored Aug 10, 2024
1 parent 3558839 commit 7ce1c91
Show file tree
Hide file tree
Showing 9 changed files with 781 additions and 699 deletions.
245 changes: 74 additions & 171 deletions docs/getting-started/getting-started-rest/01-setup-basic-syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,66 @@ pagination_next: getting-started/getting-started-rest/02-operations-responses #

## Introduction

Welcome to the first part of our tutorial on using TypeSpec to define REST APIs with HTTP. In this section, we'll introduce you to TypeSpec, help you set up your environment, and cover the basic syntax and structure of TypeSpec. By the end of this section, you'll have a solid foundation to build upon in the subsequent sections.
Welcome to our tutorial on using TypeSpec to define REST APIs with HTTP. In this section, we'll introduce you to TypeSpec, help you set up your environment, and cover the basic syntax and structure of TypeSpec. By the end of this section, you'll have a solid foundation to build upon in the subsequent sections.

### What is TypeSpec?

TypeSpec is a language and toolset developed by Microsoft for defining data models and service APIs. It provides a structured way to describe the shape and behavior of data and services, ensuring consistency and reducing errors in API development. With TypeSpec, you can generate code, documentation, and other artifacts from your API definitions, making it easier to maintain and evolve your services. Microsoft uses TypeSpec internally to define APIs for various products and services, including Azure.

TypeSpec is used to define the **interface** of your API, which clients will use to interact with resources provided by your service. This includes specifying the operations, request and response models, and error handling mechanisms. The actual API logic is implemented in the backend service, which processes the requests and communicates with the database.

Before we start writing TypeSpec code, we need to set up our development environment. For detailed instructions on setting up your environment, please refer to the [Installation Guide](../../introduction/installation.md).

### Summary of Setup and Installation

1. **Install Node.js**: Download and install Node.js from [nodejs.org](https://nodejs.org/).
1. **Install Node.js**: Download and install Node.js from [nodejs.org](https://nodejs.org/). This will also install npm, the Node.js package manager. The minimum versions required are Node.js 20.0.0 and npm 7.0.0.
2. **Install TypeSpec CLI**: Run `npm install -g @typespec/compiler` to install the TypeSpec CLI.
3. **Verify Installation**: Run `tsp --version` to verify that the TypeSpec CLI is installed correctly.
4. **Create a New Project**:
- Run `tsp init` and select the `Generic REST API` template.
- Run `tsp install` to install dependencies.
- Run `tsp compile .` to compile the initial file. You can also run `tsp compile . --watch` to automatically compile changes on save.
- Run `tsp compile .` to compile the initial file.
- Run `tsp compile . --watch` to automatically compile changes on save.

### Project Structure Overview

Once you've completed these steps, you'll have a basic TypeSpec project set up. Here's an overview of the files and directories in your TypeSpec project:

```
Project Root
├── main.tsp
├── tspconfig.yaml
├── package.json
├── node_modules/
└── tsp-output/
```

- **main.tsp**: Entry point for TypeSpec definitions.
- **tspconfig.yaml**: TypeSpec compiler configuration.
- **package.json**: Project metadata and dependencies.
- **node_modules/**: Installed dependencies.
- **tsp-output/**: Generated files.
- **openapi.yaml**: Generated OpenAPI specification.

As we work through the tutorial, keep the openapi.yaml file open in Visual Studio or VS Code to watch the API specification evolve as we make changes.

## Basic Syntax and Structure

Now that we have our environment set up, let's dive into the basic syntax and structure of TypeSpec. We'll start with a simple example to illustrate the key concepts.
Now that we have our environment set up, let's dive into the basic syntax and structure of TypeSpec. We'll create a simple REST API for a pet store by introducing concepts in a layered fashion, increasing complexity as we progress through the tutorial.

As the tutorial advances and the code examples grow more complex, we'll highlight changes in the code to help you easily spot where new lines have been added.

### Import and Using Statements

Before defining models and services, we need to import the necessary TypeSpec libraries and make them available in our namespace.

As we progress through the tutorial, you can follow along by updating the `main.tsp` file in your project and compiling the changes to see the results reflected in the generated `openapi.yaml` specification.

In most cases throughout this tutorial, you can alternatively use the `Try it` feature with the code samples to view the generated OpenAPI spec in your browser via the TypeSpec Playground.
You can also alternatively use the `Try it` feature with the code samples to quickly view the generated OpenAPI spec in your browser via the TypeSpec Playground.

Let's begin by adding the following import and using statements to the `main.tsp` file:

```typespec
```tsp tryit="{"emit": ["@typespec/openapi3"]}"
import "@typespec/http";
using TypeSpec.Http;
Expand All @@ -47,76 +78,7 @@ In this example:
- `import` statement brings in the [TypeSpec HTTP library](../../libraries/http/reference/), which provides the decorators and models we'll be using to define our REST API.
- `using` statement makes the imported library available in the current namespace, allowing us to use its features and decorators.

### Understanding Models

In TypeSpec, a [model](../../language-basics/models.md) is a fundamental building block used to define the structure of data. Models are used to represent entities, such as a `Pet`, with various properties that describe the entity's attributes.

### Example: Defining a Simple Model

Let's define a simple model for a `Pet`:

```typespec
import "@typespec/http";
using TypeSpec.Http;
model Pet {
id: int32;
name: string;
age: int32;
kind: petType;
}
enum petType {
dog: "dog",
cat: "cat",
fish: "fish",
bird: "bird",
reptile: "reptile",
}
```

In this example:

- The `model` keyword is used to define a new model named `Pet`.
- The `Pet` model has four properties: `id`, `name`, `age`, and `kind`.
- The `petType` [`enum`](../../language-basics/enums.md) defines possible values for the `kind` property.

### Example: Adding Validation Annotations

We can add [validation](../../language-basics/values#validation) annotations to our model properties to enforce certain constraints:

```typespec
import "@typespec/http";
using TypeSpec.Http;
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",
}
```

In this example:

- `@minLength(1)` ensures that the `name` property has at least one character.
- `@minValue(0)` and `@maxValue(100)` ensure that the `age` property is between 0 and 100.
**NOTE: Your generated project file likely already has these import/using statements, plus import/using for the `@typespec/openapi3` library. The `@typespec/openapi3` library is necessary for emitting the OpenAPI specification file but is not required for creating our Pet Store API in TypeSpec. Remove them from your `main.tsp` file so your code matches the example above.**

## Defining a REST Service

Expand All @@ -126,23 +88,27 @@ A REST service in TypeSpec is defined using the [`@service`](../../standard-libr

Let's start by defining a simple REST service for a Pet Store:

```typespec
```tsp tryit="{"emit": ["@typespec/openapi3"]}"
import "@typespec/http";
using TypeSpec.Http;
// highlight-start
@service({
title: "Pet Store",
})
@server("https://example.com", "Single server endpoint")
namespace PetStore;
// highlight-end
```

In this example:

- The `@service` decorator is used to define a service with the title "Pet Store".
- The `@server` decorator specifies the server endpoint for the service, which is "https://example.com".

**OpenAPI Comparison**: In OpenAPI, this is similar to defining the `info` object (which includes the title) and the `servers` array (which includes the server URL).

**NOTE: This code will not compile as-is because we've not yet defined a `namespace` for these decorators to apply to. We'll cover that topic next.**

## Organizing with Namespaces

[Namespaces](../../language-basics/namespaces.md) in TypeSpec help you organize your models and operations logically. They act as containers for related definitions, making your API easier to manage and understand.
Expand All @@ -151,7 +117,7 @@ In this example:

Let's create a namespace for our Pet Store service:

```typespec
```tsp tryit="{"emit": ["@typespec/openapi3"]}"
import "@typespec/http";
using TypeSpec.Http;
Expand All @@ -160,64 +126,27 @@ using TypeSpec.Http;
title: "Pet Store",
})
@server("https://example.com", "Single server endpoint")
// highlight-next-line
namespace PetStore;
```

In this example:

- The `namespace` keyword is used to define a namespace named `PetStore`.
- The `namespace` keyword is used to define a top-level namespace named `PetStore`.
- All models and operations related to the Pet Store service will be defined within this namespace.
- The first use of namespace defines the top-level namespace and does not require brackets. This is because it serves as the primary container for all related definitions.
- Any subsequent namespaces defined within this top-level namespace will require brackets {} to indicate that they are nested within the top-level namespace.

## Adding Models to the Namespace

Next, we'll add the `Pet` model we defined earlier to our `PetStore` namespace.

### Example: Adding the Pet Model

```typespec
import "@typespec/http";
using TypeSpec.Http;
**OpenAPI Comparison**: In OpenAPI, namespaces are similar to using tags to group related operations and definitions.

@service({
title: "Pet Store",
})
@server("https://example.com", "Single server endpoint")
namespace PetStore;
model Pet {
id: int32;
@minLength(1)
name: string;
@minValue(0)
@maxValue(100)
age: int32;
kind: petType;
}
## Defining Models

enum petType {
dog: "dog",
cat: "cat",
fish: "fish",
bird: "bird",
reptile: "reptile",
}
```

In this example:

- The `Pet` model is defined within the `PetStore` namespace.
- The model includes validation annotations to enforce constraints on the properties.
- The `petType` enum is also defined within the `PetStore` namespace.

## Defining HTTP Operations
In TypeSpec, a [model](../../language-basics/models.md) is a fundamental building block used to define the structure of data. Models are used to represent entities, such as a `Pet`, with various properties that describe the entity's attributes.

Now that we have our service, namespace, and model defined, let's add some HTTP [operations](../../language-basics/operations.md) to interact with our `Pet` model. We'll start with a simple `GET` operation to list all pets.
### Example: Defining a Simple Model

### Example: Defining a GET Operation
Let's define a simple model for a `Pet`:

```tsp tryit="{"emit": ["@typespec/openapi3"]}"
import "@typespec/http";
Expand All @@ -230,16 +159,11 @@ using TypeSpec.Http;
@server("https://example.com", "Single server endpoint")
namespace PetStore;
// highlight-start
model Pet {
id: int32;
@minLength(1)
name: string;
@minValue(0)
@maxValue(100)
age: int32;
kind: petType;
}
Expand All @@ -250,25 +174,20 @@ enum petType {
bird: "bird",
reptile: "reptile",
}
@route("/pets")
namespace Pets {
@get
op listPets(): {
@body pets: Pet[];
};
}
// highlight-end
```

In this example:

- The `@route` decorator is used to define the base path for the `Pets` namespace.
- The `@get` decorator defines a `GET` operation named `listPets`.
- The `listPets` operation returns a list of `Pet` objects in the response body.
- The `model` keyword is used to define a new model named `Pet`.
- The `Pet` model has four properties: `id`, `name`, `age`, and `kind`.
- The `petType` [`enum`](../../language-basics/enums.md) defines possible values for the `kind` property.

**OpenAPI Comparison**: In OpenAPI, this is similar to defining a `schema` object under the `components` section, where you define the structure and properties of your data models.

### Example: Defining a GET Operation with Path Parameter
### Example: Adding Validation Annotations

Let's add another `GET` operation to retrieve a specific pet by its `petId`.
We can add [validation](../../language-basics/values#validation) annotations to our model properties to enforce certain constraints:

```tsp tryit="{"emit": ["@typespec/openapi3"]}"
import "@typespec/http";
Expand All @@ -284,10 +203,13 @@ namespace PetStore;
model Pet {
id: int32;
// highlight-next-line
@minLength(1)
name: string;
// highlight-next-line
@minValue(0)
// highlight-next-line
@maxValue(100)
age: int32;
Expand All @@ -301,36 +223,17 @@ enum petType {
bird: "bird",
reptile: "reptile",
}
@route("/pets")
namespace Pets {
@get
op listPets(): {
@body pets: Pet[];
};
@get
op getPet(@path petId: int32): {
@body pet: Pet;
} | {
@body error: NotFoundError;
};
}
@error
model NotFoundError {
code: "NOT_FOUND";
message: string;
}
```

In this example:

- The `getPet` operation retrieves a specific pet by its `petId` and returns it in the response body.
- If the pet is not found, it returns a `NotFoundError`, a custom error we've defined within the PetStore namespace. We'll cover error handling in more detail in a later section.
- `@minLength(1)` ensures that the `name` property has at least one character.
- `@minValue(0)` and `@maxValue(100)` ensure that the `age` property is between 0 and 100.

**OpenAPI Comparison**: In OpenAPI, this is similar to using `minLength`, `minimum`, and `maximum` constraints within the `schema` object.

## Conclusion

In this section, we introduced you to TypeSpec, set up the development environment, and covered the basic syntax and structure of TypeSpec. We defined a simple model with validation annotations, created a REST service with a title and server endpoint, organized our API using namespaces, and added a model and HTTP operations.
In this section, we introduced you to TypeSpec, set up the development environment, and covered basic language syntax and structure. We defined a simple REST service, organized our API using namespaces, and defined a model with validation annotations.

In the next section, we'll dive deeper into defining more HTTP operations and handling different types of responses.
With this foundational knowledge, you're now ready to dive deeper into defining operations and handling different types of responses in your REST API. In the next section, we'll expand our API by adding CRUD operations.
Loading

0 comments on commit 7ce1c91

Please sign in to comment.