=========================================== Making API Requests Using Invoke-RestMethod =========================================== `Invoke-RestMethod `_ is a PowerShell cmdlet that provides the ability to send requests from the command line to a REST API. ``Invoke-RestMethod`` can be used to make web requests to any server, but is specifically intended to work with REST APIs that use JSON as their data representations. .. admonition:: Note The `Invoke-WebRequest `_ cmdlet is a more generalized tool for working with other formats like XML or HTML. Command-Line REST ================= Throughout this class, we have used Postman as a way for making requests to a RESTful API. Postman offers a GUI that is a very pleasant interface to work with. However, a GUI is not always the best interface for a given job. A benefit of making requests from a CLI is that you can combine as many requests as necessary into a single script. This creates the ability to automate interactions with a RESTful API. ``Invoke-RestMethod`` ===================== Similarly to Postman, ``Invoke-RestMethod`` allows you to fully configure each HTTP request including setting the: - URI - Method - Headers - Body The JSON responses received by an ``Invoke-RestMethod`` call are automatically converted from a JSON-formatted string to a `PSCustomObject `_. The fields of the JSON response are then accessible as properties of this object using dot notation. .. admonition:: Tip These response objects can be used directly within a PowerShell session, in a script, or can be saved to a JSON file. Open-Notify Examples ==================== Open-Notify is a publicly available REST API that returns information about astronauts in space. The `Open-Notify API `_ contains live data that is continuously updated. Let's explore this API by making a simple ``GET`` request for its ``Astros`` resource using ``Invoke-RestMethod``. Astronaut Resource Shapes ------------------------- The ``Astros`` resource has the following shape: .. sourcecode:: js :caption: the Astros resource shape { message: integer, number: integer, people: Person[] } The ``people`` field is an array of ``Person`` resources with the following shape: .. sourcecode:: js :caption: Person Resource shape { name: string, craft: string } Making a Request ---------------- Let's make a ``GET`` request for the ``Astros`` resource. If you don't specify the request method it will default to ``GET``. ``Invoke-RestMethod`` will convert the JSON response to a ``PSCustomObject``. By default, these custom objects are printed in the terminal in a table presentation: .. sourcecode:: none > Invoke-RestMethod -URI "http://api.open-notify.org/astros.json" message number people ------- ------ ------ success 5 {@{craft=ISS; name=Chris Cassidy}, @{craft=ISS; name=Anatoly Iv… Grouping to Access Fields of the JSON Response ---------------------------------------------- Using the grouping operator, we can access the ``people`` array property of the custom object in the following way: .. sourcecode:: none > (Invoke-RestMethod -URI "http://api.open-notify.org/astros.json").people craft name ----- ---- ISS Chris Cassidy ISS Anatoly Ivanishin ISS Ivan Vagner ISS Doug Hurley ISS Bob Behnken .. admonition:: Note The grouping operator will cause the ``Invoke-RestMethod`` to be executed *first*. The resulting custom object can then have its properties accessed using dot notation on the closing parenthesis: ``)``. Piping to Access Nested Fields ------------------------------ Because we are working with objects, we can filter the response down further by piping the ``people`` array object to the ``Select-Object`` cmdlet: .. sourcecode:: none > $uri = "http://api.open-notify.org/astros.json" > (Invoke-RestMethod -URI $uri).people | Select-Object -Property name name ---- Chris Cassidy Anatoly Ivanishin Ivan Vagner Doug Hurley Bob Behnken Storing Response Objects in a Reusable Variable ----------------------------------------------- Storing the result in a variable becomes useful, so that we don't have to keep making the same request to access its data: .. sourcecode:: powershell > $webRequest = Invoke-RestMethod -URI "http://api.open-notify.org/astros.json" We can then work with the data through the variable. For example, we can access the ``people`` field: .. sourcecode:: powershell > $webRequest.people craft name ----- ---- ISS Chris Cassidy ISS Anatoly Ivanishin ISS Ivan Vagner ISS Doug Hurley ISS Bob Behnken We can also access the nested ``name`` field of one of the astronauts by chaining property and array access: .. sourcecode:: powershell > $webRequest.people[0].name Chris Cassidy Sorting Response Data --------------------- We can even use our variable to control how the ``people`` array is sorted by piping it to the ``Sort-Object`` cmdlet: .. sourcecode:: powershell > $webRequest.people | Sort-Object -Property name craft name ----- ---- ISS Anatoly Ivanishin ISS Bob Behnken ISS Chris Cassidy ISS Doug Hurley ISS Ivan Vagner Converting to Other Formats --------------------------- We can combine these steps in a longer pipe that: #. Accesses the ``people`` array field #. Sorts each ``Person`` element by their nested ``name`` field #. Converts the sorted array into a CSV format .. sourcecode:: powershell > $webRequest.people | Sort-Object -Property name | ConvertTo-Csv "craft","name" "ISS","Anatoly Ivanishin" "ISS","Bob Behnken" "ISS","Chris Cassidy" "ISS","Doug Hurley" "ISS","Ivan Vagner" Saving and Loading as CSV Files ------------------------------- In many cases it is beneficial to save transformed responses to a file for later use. Rather than just printing the converted results we can use the ``Export-Csv`` cmdlet to write to a file: .. sourcecode:: powershell > $webRequest.people | Sort-Object -Property name | Export-Csv "people.csv" You can then use the ``Get-Content`` cmdlet to view the CSV contents *as strings*: .. sourcecode:: powershell > Get-Content people.csv "craft","name" "ISS","Anatoly Ivanishin" "ISS","Bob Behnken" "ISS","Chris Cassidy" "ISS","Doug Hurley" "ISS","Ivan Vagner" Saving and loading as JSON files -------------------------------- If we wanted to save in a JSON format we would need to add an additional step in our pipeline to convert the custom object back to a JSON string. We use the ``ConvertTo-Json`` cmdlet to accomplish this *serialization* from an object back to a JSON string: .. sourcecode:: powershell :caption: Windows/PowerShell > $webRequest.people | Sort-Object -Property name | ConvertTo-Json | Set-Content "people.json" .. admonition:: Note We can also split up this pipeline to make it more readable: .. sourcecode:: none :caption: Windows/PowerShell > # split for readability > $SortedPeople = $webRequest.people | Sort-Object -Property name > $SortedPeople | ConvertTo-Json | Set-Content "people.json" This approach is invaluable for practicing with data transformations. Whereas a variable in our PowerShell terminal will disappear after closing, a file can be reused indefinitely and shared with others. You can then load the JSON contents *as a string* using ``Get-Content``: .. sourcecode:: none :caption: Windows/PowerShell > Get-Content "people.json" [ { "craft": "ISS", "name": "Anatoly Ivanishin" }, { "craft": "ISS", "name": "Bob Behnken" }, { "craft": "ISS", "name": "Chris Cassidy" }, { "craft": "ISS", "name": "Doug Hurley" }, { "craft": "ISS", "name": "Ivan Vagner" } ] However, in order to work with the JSON contents as custom objects we need to convert it back (*deserialize*) using the ``ConvertFrom-Json`` cmdlet. This will enable dot-notation access of fields like in the original ``Invoke-RestMethod`` output: .. sourcecode:: powershell :caption: Windows/PowerShell > Get-Content "people.json" | ConvertFrom-Json craft name ----- ---- ISS Anatoly Ivanishin ISS Bob Behnken ISS Chris Cassidy ISS Doug Hurley ISS Ivan Vagner The ``Invoke-RestMethod`` cmdlet is a powerful tool for working with APIs. When combined with our knowledge of PowerShell, we have many options for interacting with a REST API and transforming the data we receive. ``CodingEventsAPI`` Examples ============================ Let's test this out with our Coding Events API. To keep things simple, let's use the ``1-sqlite`` branch so we don't need to worry about setting up a database, a secrets manager, or AADB2C. Run this branch to start the Coding Events API on your local machine. ``GET`` Example --------------- To get a collection of coding events you could use: .. sourcecode:: powershell > Invoke-RestMethod -Uri "http://localhost:5000/api/events" To get an individual coding event entity you could use: .. sourcecode:: powershell > $CodingEventId = 1 > Invoke-RestMethod -Uri "http://localhost:5000/api/events/$CodingEventId" ``DELETE`` Example ------------------ To delete an existing coding event entity you could use: .. sourcecode:: powershell > $CodingEventId = 1 > $uri = "http://localhost:5000/api/events/$CodingEventId" > Invoke-RestMethod -Method "Delete" -Uri $uri ``POST`` Example ---------------- To create a new coding event we need to use two additional options: - ``-Method``: to set the ``POST`` HTTP method - ``-Body``: to define the body of the ``POST`` request To provide the body of the request you can use a `HashTable object `_ or a `here-string `_. The ``HashTable`` object is simple to create: .. sourcecode:: powershell > $body = @{ Title = "Halloween Hackathon!" Description = "A gathering of nerdy ghouls..." Date = "2020-10-30" } .. admonition:: Note The ``HashTable`` object *does not have any commas* and uses the ``=`` assignment operator for defining each key-value entry. However, before it can be used in the request it *must be converted to JSON* with an appropriate ``Content-Type`` header. We can use: - ``ConvertTo-Json``: in a grouped expression to serialize the ``HashTable`` as a JSON string - The ``-ContentType`` option: to automatically set the ``Content-Type`` header of ``application/json`` .. sourcecode:: powershell :caption: Windows/PowerShell > $uri = "http://localhost:5000/api/events" > Invoke-RestMethod -Method "Post" -Uri $uri -Body ($body | ConvertTo-Json) -ContentType "application/json" Using a JSON File ^^^^^^^^^^^^^^^^^ You can also load the body from a JSON file. This allows you to use existing files or a GUI editor to create the JSON body in a more intuitive way. Let's assume we have a file ``~\coding-event.json`` with the following contents: .. sourcecode:: none :caption: Windows/PowerShell > Get-Content ~\coding-event.json { "Title": "test title is long", "Description": "test description goes here", "Date": "2020-10-31" } We could use this file as the contents of the request body using a grouped expression: .. sourcecode:: powershell :caption: Windows/PowerShell > Invoke-RestMethod -Method Post -Uri $uri -Body (Get-Content ~\coding-event.json) -ContentType "application/json" .. admonition:: Tip You can use any of these ``-Body`` defining approaches for creating and adding bodies to ``PUT`` and ``PATCH`` requests as well. When used on a ``GET`` request the body will be converted to query string parameters in the URI. Continue Learning ================= ``Invoke-RestMethod``, like Postman, has many additional options we can use to further configure requests. You can look over the documentation of `Invoke-RestMethod `_ to get an understanding of its capabilities. You can work with any RESTful APIs using the ``Invoke-RestMethod`` cmdlet. To continue practicing you can work with any publicly available APIs like the `GitHub Developer API `_.