@@ -0,0 +1,200 @@
3+OpenAPI is language-agnostic format for API specifications. It is structured as JSON or YAML
4+document and can be used to communicate API documentation between the backend and its clients, like
7+The OpenAPI specification itself is available at https://swagger.io/specification/. It is supported
8+by various tools, like [swagger-ui](https://swagger.io/tools/swagger-ui/) — a tool that
9+visualizes OpenAPI document and allows to send requests to the backend it describes, or
10+[swagger-codegen](https://swagger.io/tools/swagger-codegen/), which can generate client code in a
11+variety of languages given the specification.
13+Since Servant backends already contain a comprehensive description of the API they provide, it is
14+fairly easy to generate OpenAPI specification based on that description. This is achieved with
15+[servant-openapi3](https://hackage.haskell.org/package/servant-openapi3) package, which is based on
16+older `servant-swagger`, targeted at second version of OpenAPI specification (then called Swagger).
18+This cookbook demonstrates how to use `servant-openapi3` and how to integrate interactive schema
19+browser with your backend.
23+Let's start with an API of an example TODO service:
26+{-# LANGUAGE DataKinds #-}
27+{-# LANGUAGE TypeOperators #-}
28+{-# LANGUAGE DeriveGeneric #-}
29+{-# LANGUAGE DeriveAnyClass #-}
30+{-# LANGUAGE DerivingStrategies #-}
31+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
40+import Servant.OpenApi
41+import Servant.Swagger.UI
43+import Network.Wai.Handler.Warp as Warp
45+-- | A single Todo entry.
47+ { created :: Int -- ^ Creation datetime.
48+ , summary :: Text -- ^ Task summary.
50+ deriving stock (Show, Generic)
51+ deriving anyclass (ToSchema, ToJSON, FromJSON)
53+-- | A unique Todo entry ID.
54+newtype TodoId = TodoId Int
55+ deriving stock (Show, Generic)
56+ deriving newtype (ToJSON, FromHttpApiData)
57+ deriving anyclass (ToParamSchema, ToSchema)
59+-- | The API of a Todo service.
61+ = "todo" :> Description "Get all TODO items"
62+ :> Get '[JSON] [Todo]
63+ :<|> "todo" :> Description "Add a new TODO item"
64+ :> ReqBody '[JSON] Todo :> Post '[JSON] TodoId
65+ :<|> "todo" :> Description "Get a TODO item by its id"
66+ :> Capture "id" TodoId :> Get '[JSON] Todo
67+ :<|> "todo" :> Description "Update an existing TODO item by its id"
68+ :> Capture "id" TodoId :> ReqBody '[JSON] Todo :> Put '[JSON] TodoId
71+Notice that all API endpoints are decorated with `Description` (coming from `servant` itself): these
72+descriptions will propagate to the OpenAPI document automatically.
76+We are ready to define OpenAPI document for our `TodoAPI`. Everything you need to do for that is to
77+use `toOpenApi` function from `servant-openapi3` package:
80+-- | OpenAPI spec for Todo API.
81+todoOpenApi :: OpenApi
82+todoOpenApi = toOpenApi (Proxy :: Proxy TodoAPI)
85+This is possible since we've derived `ToSchema` for `Todo` and `ToParamSchema` for `TodoId` (needed
86+since the type is used in URLs) instances — and this is everything that is needed to generate
87+the OpenAPI 3.0 specification for our API. All of this is thanks to `Generic`-based schema generator
88+found in `openapi3` and `servant-openapi3` packages.
90+Of course, you can customize the schema in many ways, see the documentation for
91+[`openapi3`](https://hackage.haskell.org/package/openapi3) package.
93+The generated schema looks something like this:
105+ "description": "Get all TODO items",
109+ "application/json;charset=utf-8": {
112+ "$ref": "#/components/schemas/Todo"
137+ "minimum": -9223372036854775808,
139+ "maximum": 9223372036854775807
145+ "minimum": -9223372036854775808,
147+ "maximum": 9223372036854775807
154+The schema can be pasted into the [Swagger editor](https://editor.swagger.io/), which will nicely
155+display the generated schema.
157+## Integrating schema browser into the backend
159+Or, the schema browser can be integrated into the backend itself. This is done via
160+`servant-swagger-ui` package, which embeds `swagger-ui` into the Servant backend.
162+First, define a sub-api that will serve the documentation:
165+type DocsAPI = SwaggerSchemaUI "swagger-ui" "swagger.json"
168+And a full API for your backend, which combines your endpoints and `DocsAPI`:
171+type API = DocsAPI :<|> TodoAPI
174+`SwaggerSchemaUI` describes an API that will serve the interactive schema browser at `/swagger-ui`
175+of your server and the specification in JSON format at `/swagger.json`. Of course, both paths are
178+A handler for `SwaggerSchemaUI`, called `swaggerSchemaUIServer`, expectes one argument: the
179+specification itself. In our case, it's `todoOpenApi`.
182+todoServer :: Servant.Server API
183+todoServer = swaggerSchemaUIServer todoOpenApi
184+ :<|> error "The actual TODO API is not implemented"
187+Now the server can be run as usual:
192+ Warp.run 5000 $ serve (Proxy :: Proxy API) todoServer
195+Run this example, navigate to http://localhost:5000/swagger-ui and you will see the interactive
198+
200+You can make requests in this UI and they will be sent to your backend as expected.