# How to generate an OpenAPI schema from your code

An [OpenAPI schema](https://www.openapis.org/) (also called an OpenAPI specification) is a text description of your web service in detail. Your customers and automated tools can use this document to interact with your service with the confidence that they know exactly what each operation does and how to use it.

Some companies start designing their software service in an OpenAPI schema and use that schema to generate their server code. This is called the schema-first approach.

But it's more likely you started building software to service a market, then realized the benefits of providing OpenAPI tools to your clients, and now want to know how to create and maintain an OpenAPI schema from your existing server code. This is the code-first approach. This guide will explain how to generate a schema from code, and link to all the necessary tools in the programming language you use.

But first, let's summarize the benefits of using OpenAPI and the variations of the code-first approach that you can choose from.

## The benefits of OpenAPI

If you don't have an OpenAPI schema, you still need to write documentation, with examples, explaining to your clients how they can call your API. You also need to update this documentation whenever the code changes on your server.

Writing this documentation in the standard OpenAPI format provides the following benefits:

- Enables your clients to browse the schema in their tool of choice, such as [Swagger UI](https://swagger.io/tools/swagger-ui), with good usability.
- Allows the schema to be used for automatic SDK generation, enabling your clients to call your server directly from code without needing to handle REST web calls or use curl. Even if you don't generate an SDK, using an OpenAPI schema to validate the code that clients use to call your service can reduce confusion and errors.
- Supports the generation of server code stubs with statically typed request parameters and responses that you can use to validate that your server code matches the schema.

## Different workflows to support OpenAPI

If you take a code-first approach, you can either annotate your existing code with special comments that an automated tool uses to generate an OpenAPI schema, or start coding in a meta-framework that generates both the server code and the OpenAPI schema. (We call it a meta-framework because it sits above both your code and your schema, controlling both.)

There are two types of annotation:

- Text comments, which are not part of the programming language in which your server is written.
- Attributes, which are valid code in the programming language.

Attributes are superior, as the compiler and your IDE can check that your annotations are syntactically valid code as you work. Attributes can also be refactored, unlike comments.

The danger of using annotations is human error. Programmers need to update annotations correctly whenever they change an operation, but they may forget to or make mistakes. Even when attribute annotations are syntactically correct, they may still describe the operation incorrectly. This will cause your code and schema to drift apart, potentially causing errors when clients try to call your API.

Using a meta-framework, on the other hand, automatically synchronizes your code and schema but requires writing code in the new framework, adding a compilation step, and learning a new tool.

A third option that falls between annotations and meta-frameworks is to use an OpenAPI-aware web framework. These frameworks are designed with native support for OpenAPI features and can automatically generate an OpenAPI schema from the code you write, usually with more detail than that provided in annotations. OpenAPI-aware frameworks, however, tend to be very new, and the most popular (and older) frameworks, like Django and Laravel, are not OpenAPI-aware.

Note that the workflow options we discuss here depend on the availability of tools to support them in your programming language. Popular web languages will have tools for a range of options, but obscure languages may not. You will also find it easier to use a statically typed language, like Go, than a dynamic one, like PHP.

The following process will probably work best for most companies wanting to support OpenAPI:

- Generate an OpenAPI schema for your code, as we describe in the rest of this article.
- Use the new schema as your principal source from now on and generate server code from the schema rather than the other way around. (There is a section explaining schema-first workflows at the end of this guide.)
- Generate SDKs from the schema to make it simple for your clients to call your API.

If you want to keep your code as the principal source, you may want to include an automated reminder in your version-control process to tell programmers to update annotations whenever they commit code.

## Four techniques to extract an OpenAPI schema

The next four sections will show you different ways to generate an OpenAPI schema document from your existing code by:

- Recording network traffic
- Adding comment annotations
- Adding attribute annotations and changing your code to use an OpenAPI-aware framework
- Rewriting your code using a meta-framework

Each technique varies depending on the programming language. This guide provides examples in Go, as it's a popular, statically typed language specifically designed for web services.

The first thing to check is whether your web server framework already natively supports OpenAPI. If it does, you can stop reading this article and follow the framework documentation on how to access the schema the framework automatically creates. If not, read on to discover your options.

If you don't have an API yet but want to learn about OpenAPI for future projects, choose a server framework and language with native OpenAPI support. Alternatively, start by documenting your proposed API as a schema using a Swagger tool, then use a framework that supports code generation from the schema.

## Use HAR and APIGit to generate a schema

The first way to create a schema is to use a tool that listens to your network calls and extracts the schema automatically from the HTTP calls it sees. Generating a schema from network traffic does not depend on programming language. The process is the same for all languages.

The benefit of this approach is that you don't have to manually read your code and document (potentially with mistakes) what the calling contract should be. Instead, the network tool will tell you what actual data is sent, allowing you to spot any unintended parameters when reading through the schema after creation.

The disadvantage is that you have to manually call every operation in your API with all possible parameters to ensure the generator records all variations and creates an accurate schema.

For smaller APIs, you might prefer manually adding annotations or writing code to generate a schema. But for any other project, we recommend using this network listener approach, even in conjunction with other approaches, as it provides an automated review of your existing API.

### HAR example

Let's use the standard Pet Store OpenAPI example to demonstrate how to use a network listener.

- Browse to https://petstore31.swagger.io/#/pet/addPet in a Chromium-based browser.
- Open the developer tools (F12) and select the **Network** tab.
- Select only **Fetch/XHR**.
- Click **Execute** in the web page to start a network call to the API to add a pet.
- In the developer tools, click the `pet` line that appears to see the details of the request and response.
  ![XHR](/assets/openapi/guide/xhr.png)
- Also execute a pet update (PUT). (In reality, you want to call every endpoint in your API, but we don't need to do that now.)
- In the network tab, click the download arrow icon in the top row.
- Save an HAR file (HTTP archive) to your desktop.

Several tools will convert HAR to an OpenAPI schema. We'll use APIGit.

- Browse to https://app.apigit.com.
- Create an account.
- Create a repository named `pet` (any name will do).
- In the sidebar, browse to **API Documents**, then click the plus sign to add a document.
- Upload the HAR file you saved previously.
- Set the path name to `pet.yaml`.
- Click **Create**.
- Download the YAML file. This is your OpenAPI schema.

![APIGit](/assets/openapi/guide/apigit.png)

You can view the schema in a text editor or in a more visually descriptive way in an OpenAPI editor.

- Browse to https://editor-next.swagger.io.
- Import your YAML file.

![Swagger editor](/assets/openapi/guide/editornext.png)

The editor will show errors and duplicated objects that could be moved into the components section and shared.

[Other network capture tools](https://tools.openapis.org/categories/all.html) are available. The all-in-one OpenAPI DevTools plugin from Chrome Web Store offers network recording and OpenAPI schema generation.

## Comment annotation

Once you know what all the operations and parameters for your API should be in OpenAPI terms – either by using the generator as discussed in the previous section or by reading the list of OpenAPI data types – you can annotate your existing code with comments. A generator will then use the comments to output an OpenAPI schema.

As mentioned in the introduction, we recommend using attribute annotations instead of comment annotations whenever possible. If your server framework doesn't support attribute annotations, this approach is an alternative option.

You should consider whether to continue using your existing framework or switch to one with native support for OpenAPI. In the long term, adding and maintaining annotations might be more or less work than rewriting and maintaining OpenAPI-aware code.

Comment annotation, like attribute annotation and using an OpenAPI meta-framework, depends on the programming language you use.

### Example comment annotation in Go

The tool used here is [go-swagger3](https://github.com/parvez3019/go-swagger3).

- In any folder on your computer, create a folder called `src`.
- Create a file in `src` called `main.go` and add the content below to it.

  ```go
  package main

  import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
  )

  type RequestBody struct {
    Name string `json:"name"`
  }

  func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/hello", helloHandler)

    log.Println("Server started on port 8080")
    log.Fatal(http.ListenAndServe(":8080", mux))
  }

  func helloHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
  	  http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
  	  return
    }

    var requestBody RequestBody
    err := json.NewDecoder(r.Body).Decode(&requestBody)
    if err != nil {
  	  http.Error(w, "Bad request", http.StatusBadRequest)
  	  return
    }

    response := fmt.Sprintf("Hello %s", requestBody.Name)
    w.Header().Set("Content-Type", "text/plain")
    w.Write([]byte(response))
  }
  ```

  This minimal web service has a single POST endpoint, `/hello` that says hello to the name sent as a string parameter called `name`.

- Build and run the service with the commands below to see that it works. You don't need to install Go on your machine, but you do need Docker. Docker will run Go in a container in a security sandbox so it has no access to the rest of your files. You then call the service in the terminal with curl.

  ```sh
  docker run --rm  -v $(pwd)/src:/app -w /app golang:1.23-alpine3.19 /bin/sh -c   "go mod init helloworld && go mod vendor && go build"

  docker run --rm -p 65000:8080 -v $(pwd)/src:/app -w /app golang:1.23-alpine3.19     ./helloworld

  # In a new terminal
  curl -X POST -H "Content-Type: application/json" -d '{"name":"John"}' http://localhost:65000/hello

  # Output
  # Hello John
  ```

Now let's update the web service with some OpenAPI comments.

- Update `main.go`, adding the following at the top of the file:
  ```go
  // @Version 1.0.0
  // @Title Hello API
  // @Description Testing Go OpenAPI frameworks
  package main
  ...
  ```
- Add comments above the operation:
  ```go
  // @Title Hello
  // @Description Say hi to the name given
  // @Param  name  body  RequestBody   true  "Any name"
  // @Success  200 {string} string "Responds with 'Hello {name}'"
  // @Route /helloHandler [post]
  func helloHandler(w http.ResponseWriter, r *http.Request) {
  ...
  ```

To generate a schema from this file, you can either install go-swagger3 in the standard Go container you just used or use the dedicated go-swagger3 Docker image. The first option requires redownloading the Go modules each time, as they are not stored in your project `vendor` folder.

Run one of the two commands below.

```sh
docker run --rm -v $(pwd)/src:/app -w /app golang:1.23-alpine3.19 /bin/sh -c "go install github.com/parvez3019/go-swagger3@latest && /go/bin/go-swagger3 --main-file-path /app/main.go --module-path . --output oas.json --schema-without-pkg --generate-yaml true"