Share



Designing a RESTful Fantasy Football Stats API with Swagger and OpenAPI


Fantasy football statistics can be fun to look up, compare and contrast, and share with friends. It can also be fun designing a RESTful API for accessing all of this data using Swagger and the OpenAPI Specification.

In a few of my earlier posts I wrote tutorials on scraping football stats from the web and storing them in a SQL database. Now that we have all of this fun data stored in a useful database we should build an API for accessing it. However, before we start coding, let’s first design our API!

Note: If you’re reading this and have no idea what an API is then I suggest reading this blog post first!

Urkel Swagger

What is Swagger?

Swagger is a set of open-source tools built around the OpenAPI Specification that can help you design, build, document and consume REST APIs. Swagger provides us with a tool and a common language for describing our APIs, making developers’ lives easier and APIs more consumable. When it comes to documenting REST APIs Swagger is the industry standard, is specifically designed for REST Services, has a huge community behind it, and the end result looks great!

The major Swagger tools include:

  • Swagger Editor – Browser-based editor where you can write OpenAPI specs.
  • Swagger UI – Renders OpenAPI specs as interactive API documentation.
  • Swagger Codegen – Generates server stubs and client libraries from an OpenAPI spec.

What is the OpenAPI Specification?

OpenAPI Specification (formerly Swagger Specification) is an API description format for REST APIs. An OpenAPI file allows you to describe your entire API, including:

  • Available endpoints (/players) and operations on each endpoint (GET /players, POST /players)
  • Operation parameters input and output for each operation
  • Authentication methods
  • Contact information, license, terms of use and other information.
  • API specifications can be written in YAML or JSON. The format is easy to learn and readable to both humans and machines. The latest OpenAPI Specification can be found on GitHub: OpenAPI 3.0 Specification.

Design First Approach vs Code First Approach for API Development

Before we dive right in and start writing our Swagger specification for our Fantasy Football Stats API let’s briefly cover the “Design First Approach” vs. the “Code First Approach” for API design. The Code First approach is a more common approach to building APIs. The API is implemented first and the Swagger documentation is typically generated from the code. The Design First approach calls for the API contract to be documented first (this is where Swagger comes in!) before writing any code. A design first approach ensures a quality design before implementation begins.

So which approach is the correct one to use? Each has its pros and cons, but, personally, I prefer the design first approach. An API is a user interface for a developer - so put some effort into making it pleasant. A well designed API is better achieved with the Design First Approach, thus let’s start designing our RESTful Fantasy Football stats API!

Designing Our Fantasy Football Stats API

Where do we begin? Before opening up the online Swagger editor let’s take a step back and discuss the data we are building an API for. In one of my last posts we created a database containing data for a single Fantasy Football season, all contained in one table. Such a solution was fine for that post, but what if we want multiple seasons of data for our API? Our database would now contain Teams, their associated Players, and the players’ Stats for various Seasons. A rough design for the objects/tables containing this data might look something like this:

- Team
    -- team_id
    -- city
    -- name (Ravens, Panthers, Jaguars)
    -- abbreviation (NE, BAL, SEA, etc.)

- Season
    -- year (also serves as the id)

- Player
    -- player_id
    -- name (First and Last)
    -- height
    -- weight
    -- birthdate
    -- position
    -- number

-- Stats
    -- stat_id
    -- player_id
    -- season
    -- team_id
    -- games_played
    -- games_started
    -- pass_atts
    -- pass_yards
    -- pass_tds
    -- rec_targets
    -- rec_receptions
    -- rec_yards
    -- rec_tds
    -- rush_atts
    -- rush_yards
    -- rush_tds
    -- fumbles
    -- fumbles_lost
    -- two_point_con
    -- two_point_con_pass
    -- fan_points_ppr
Getting Started with our Swagger Spec

Time to open the online Swagger editor and start writing our API specification. For this post I went with the tried and tested Swagger specification found here.

Below is a screenshot of the Swagger Editor with the finished Fantasy Football Stats API specification already loaded. The editor is easy to use. You simply write your specification on the left-hand side of the editor and see the corresponding documentation on the right.

Swagger Editor

Our finished product, a fully-fleshed out Fantasy Football Stats API Swagger 2.0 specification, can be found here, in this GitHub repo. Simply download the JSON specification and import it into the Swagger editor if you want to tinker with the spec online in the editor.

Note: Swagger specifications can be written in either JSON or YAML. I chose JSON for this post, but it can easily be converted to YAML right in the Swagger Editor.

Swagger General Information

The top portion of our specification contains some general information, or metadata, describing our API:

{
    "swagger":"2.0",
    "info":{
        "description":"RESTful API for accessing our Fantasy Football stats data",
        "version":"1.0.0",
        "title":"Fantasy Football Stats API"
    },
    "host":"localhost:5000",
    "basePath":"/fantasystats",
    "tags":[
        {
            "name":"player",
            "description":"Acess to player data"
        },
        {
            "name":"season",
            "description":"Access to season data"
        },
        {
            "name":"stats",
            "description":"Access to stats data"
        },
        {
            "name":"team",
            "description":"Access to team data"
        }
    ],
    "schemes": [
        "https",
        "http"
      ],
    // Rest of specification would go here here.
}

What does all this mean? Let’s take a look at each attribute:

  • swagger: This is the specification we are writing against. For this post I chose Swagger 2.0.
  • info: This contains some information describing our API, including its title, description, and version number.
  • host: The host, or server where our API is located.
  • basePath: The path on the host, or server, where our API is located and can be invoked.
  • tags: Tags allow us to arrange the paths (endpoints) into named groups in the Swagger UI display. Here our groups are player, season, stats, and team.
  • schemes: The transfer protocols used by our API.
Paths and Methods

With our general information laid out we move onto the the next portion of our specification containing all of our API’s paths and CRUD methods. This section is long, so let’s just take a look at the POST method for adding a new player and the GET method for retrieving an individual player.

    "paths":{
        "/players":{
            "post":{
                "tags":[
                    "player"
                ],
                "summary":"Adds a new Player",
                "description":"Creates a new player in the database.",
                "parameters":[
                    {
                        "in":"body",
                        "name":"body",
                        "description":"Player Object",
                        "required":true,
                        "schema":{
                            "$ref":"#/definitions/Player"
                        }
                    }
                ],
                "responses":{
                    "200":{
                        "description":"successful operation",
                        "examples":{
                            "application/json":{
                                "playerId":123
                            }
                        }
                    }
                }
            }
        },
        "/players/{playerId}":{
            "get":{
                "tags":[
                    "player"
                ],
                "summary":"Gets a Player by their unique Id",
                "description":"Retrieves a player's information, including first and last name, height, weight, jersey number, and birthdate.",
                "parameters":[
                    {
                        "name":"playerId",
                        "in":"path",
                        "description":"Unique player Id",
                        "required":true,
                        "type":"integer",
                        "format":"int64"
                    }
                ],
                "responses":{
                    "200":{
                        "description":"sucessful operation",
                        "schema":{
                            "$ref":"#/definitions/Player"
                        }
                    },
                    "400":{
                        "description":"Invalid Id supplied"
                    },
                    "404":{
                        "description":"Player not found"
                    }
                }
            },
            // Remaining endpoints and methods would go here.
        }

As you can see, all of the paths and HTTP methods will be defined in this section. Each path can contain numerous methods, such as GET, POST, PUT, PATCH, or DELETE. Each method consists of various attributes:

  • tags: The tag this endpoint should be grouped under.
  • summary: Text summary of the method.
  • description: Text description of the method. The description typically provides more information than the summary.
  • parameters: The various parameters the method accepts. This includes HTTP header, query string, URI path, and request body parameters.
    • name: Name of the parameter.
    • in: Location of the parmeter, such as header, query, path, or body.
    • description: Text description of the parameter.
    • required: Boolean value indicating if the parameter is required or not.
    • type: The parameter’s type, such as number or string.
    • schema: Reference to a definition/schema describing the parameter object.
    • format: The format of the parameter’s type.
  • responses: List of responses the method can return.
    • HTTP Status Code: The HTTP status code(s) the method can return.
      • description: Description of the response for this HTTP status code.
      • schema: Reference to a definition/schema describing the response object for this HTTP status code.

Our remaining methods will be described similarly to the GET and POST methods above. I’ve omitted the remaining methods here due to length, but they can be found in the accompanying GitHub repo for this post. Now, all that is left is our definitions section which describes the various objects that make up our API’s input and output.

Definitions

Our Swagger specification concludes with our definitions section. This section allows us to define common data structures used throughout our API. For our particular Fantasy Football Stats API these data structures include our Player, Team, Season, and Stats objects. Below is the Player object, as an example:

   "definitions":{
        "Player":{
            "type":"object",
            "properties":{
                "birthdate":{
                    "type":"string",
                    "description":"Player's birthday in MM/DD/YYYY format.",
                    "example":"01/07/1997"
                },
                "height":{
                    "type":"string",
                    "description":"Player's height in FEET-INCHES format.",
                    "example":"6-2"
                },
                "name":{
                    "type":"string",
                    "description":"Player's full name, first and last.",
                    "example":"Lamar Jackson"
                },
                "number":{
                    "type":"string",
                    "description":"Player's jersey number.",
                    "example":"8"
                },
                "playerId":{
                    "type":"integer",
                    "description":"Unique identifier for the Player.",
                    "readOnly":true,
                    "example":123
                },
                "position":{
                    "type":"string",
                    "enum":[
                        "QB",
                        "RB",
                        "WR",
                        "TE"
                    ],
                    "description":"Player's position (QB, RB, WR, TE).",
                    "example":"QB"
                },
                "teamId":{
                    "type":"integer",
                    "description":"Unique identifier for the current team the Player plays for.",
                    "example":3
                },
                "weight":{
                    "type":"integer",
                    "description":"Player's weight in pounds (lbs).",
                    "example":"212"
                }
            }
        },
        // Remaining definitions would go here.
   }

The Player definition above is an object, consisting of various properties. A definition’s structure can consist of the following attributes:

  • type: The datatype of our definition. Above, our Player is an object, but string, number, and array are also valid.
  • properties: Applicable when the definition type is an “object”. The properties describe the various attributes that make up the object.

The full player definition can be found above. Our spec also needs Team, Season and Stats definitions, all of which can be found on GitHub.

Conclusion

This covers all of the sections of our Swagger spec. Again, if you want to see the spec in its entirety, check it out on GitHub.

The Swagger Editor is a handy tool, but we can also develop our API specifications locally. When working on this post I utilized SwaggerUI via its Docker image like so:

docker run -p 8081:8080 -e SWAGGER_JSON=/tmp/fantasy_football_api.json -v `pwd`/swagger:/tmp swaggerapi/swagger-ui

Run the above command from your specification’s main directory. The swagger directory referred to above is where my specification JSON file can be found. Once SwaggerUI is running you can access your Swagger spec via a web browser at http://localhost:8081.

SwaggerUI

Note: If you need to install Docker you can get it here.

That concludes my high-level introduction to both Swagger and OpenAPI. Why even go through all this trouble? There are many reasons, some which you can read more about here Happy API designing!

Subscribe

Get updates on new content straight to your inbox! Unsubscribe at anytime.

* indicates required