Home Posts Post Search Tag Search

Ash Framework 05 - API's
Published on: 2025-09-27 Tags: elixir, Blog, Side Project, LiveView, Ecto, Html/CSS, Phoenix, Ash, Framework

Chapter 4: Generating APIs Without Writing Code (85)

We now want to make an API so that other users can access our data in thier own apps.

Model Your Domain, Derive the Results
    In this chapter we will build a REST and GraphQL API for other users to access our data.

Building A JSON REST interface
    We will use some built in ASH package to generate the API that will accept requests over HTTP and the send the data in a JSON format

    Setup
        Lets add some features to the app. This will be done with and igniter install, here is what it will do: 
            ash_json_api Hex package
            Code formatting and configuration to support a new application/vnd.api+json
            New TunezWeb.AshJsonApiRouter
            A new scope for the Phoenix router.

            mix igniter.install ash_json_api

    Adding Artists to the API
        We will try and implement the CRUD for the API and as such we will need to add each resource and then make them public. We will use the extend Mix task to do so. A couple of things that this will do:
            AshJsonApi.Resource will be added to make use of the new access point
            A default API type will be added to the resource.
        Since it is the first resource for Music it will connect the pieces of the domain
            AshJsonApi.Domain will be added as an extension of the Tunez.Music domain
            Tunez.Music will be added to the list of domains in the TunezWeb.AshJsonApiRouter module.
            mix ash.extend Tunez.Music.Artist json_api

        Now we need to set some of the routes to make the actions on the Artist resource available in the API. We could add the domain or the resource to the API but it makes more sense to add a layer of obfiscation so that a user can only interact with the top layer of the domain. Head to lib/tunez/music.ex
            defmodule Tunez.Music do
                # ...
                json_api do
                        routes do
                        base_route "/artists", Tunez.Music.Artist do
                            get :read
                            index :search
                            post :create
                            patch :update
                            delete :destroy
                        end
                    end
                end
            end

        This will set some default actions for each user for each artist. GET and artist POST will create an artist, and so on. We can check the routes from a the WSL console
            mix phx.routes

        You can even use the GET route by accessing http://localhost:4000/api/json/artists/?query=and

        "What Data Gets Included in API Responses?
            So you might notice that some of the data is missing and that is becuase only the attributes that are made public are included. This is a security thing and can be changed. Lets add the biography and the previous_names to the public list, head to lib/tunez/music/artist.ex
                attributes do
                    # ...

                    attribute :previous_names, {:array, :string} do
                        default []
                        public? true
                    end

                    attribute :biography, :string do
                        public? true
                    end
                    # ...
                end

            Calculations and Aggregates are different as they can be calculation expensive so there are a few ways to make them visible to the API:
                Set them as default fields at the resource level. This will be done EVERYTIME that the resource is pulled. 
                Make it so that a user can request the specific calculation or aggreagates that they require. They will need to add the field to the query string parameters. Something like http://localhost:4000/api/json/artists?fields=name,album_count . THis is a default response in the ASH framework.

        "Creating Artists Records"
            There is quite a few routes that were created and we will just go over the post but with the right body and route you can use the API to create an artist as well. 

            The PATCH can update and the DELETE can remove an artist.

    Adding Albums to the API
        We can use the same extension to add albums to the API as well. Afterwards we will need to add in the routes and default actions that we want the API route to be able to use.
            mix ash.extend Tunez.Music.Album json_api

        Now we need to head to lib/tunez/music.ex to add in the routes as we did before
            json_api do
                routes do
                    # ...

                    base_route "/albums", Tunez.Music.Album do
                        post :create
                        patch :update
                        delete :destroy
                    end
                end

        We now have the way to work with the albums but we don't have a nice way of dealing with the artist_id that is usually pre-hidden in the HTTP request. We will need to provide a valid ID for that and we will need to head into the album.ex to make those values public. Head to lib/tunez/music/album.ex to make those changes.
            attributes do
                uuid_primary_key :id
                attribute :name, :string do
                    allow_nil? false
                    public? true
                end
                attribute :year_released, :integer do
                    allow_nil? false
                    public? true
                end
                attribute :cover_image_url, :string do
                    public? true
                end
                # ...

        "Showing Albums for a Given Artist"
            There are to ways to include related resources for a given resource. Both will require that the relationship is public. Head to lib/tunez/music/artist.ex to set that now.
                relationships do
                    has_many :albums, Tunez.Music.Album do
                        sort year_released: :desc
                        public? true
                    end
                end

        "Including Related Records"
            The easist way and it mirrors the way we have done things like this before and that is to include it in the json_api parent resource. Like this http://localhost:4000/api/json/artists?query=cove&include=albums
                json_api do
                    type "artist"
                    includes [:albums]
                end

        "Linking to a List of Related Records"
            You can add a link the the records that the user might want wit the GET call to the API. This will require heading into lib/tunez/music.ex and adding in a related relationship to the API response.
                base_route "/artists", Tunez.Music.Artist do
                    # ...
                    related(:albums, :read, primary?: true)
                end

    Generating API Documentation with OpenApiSpex
        The installation of AshJsonApi also incluced a way to build some Documentation with ease. We just need to add in the route. Head to lib/tunez_web/ash_json_api_router.ex to see what it already did. http://localhost:4000/api/json/open_api
            defmodule TunezWeb.AshJsonApiRouter do
                use AshJsonApi.Router,
                    domains: [Tunez.Music],
                    open_api: "/open_api"
            end

        Now this is just a json result with all the information but not a clear way of using it you can use the built in funcionality of swaggerui to format it much better. Head to lib/tunez_web/router.ex this will made the route api/json/swaggerui available
            scope "/api/json" do
                pipe_through [:api]

                forward "/swaggerui", OpenApiSpex.Plug.SwaggerUI,
                path: "/api/json/open_api",
                default_model_expand_depth: 4

                forward "/", TunezWeb.AshJsonApiRouter
            end

    Customizing the Generated API
        Lets tackle some low hanging fruit and make it look better and work better.

        "Adding Informative Descriptions"
            Ash allows us to set some descriptors for all the resources that we have in order to make any Documentation or calls be better understood.
                A description for a resource as a whole. or
                A description for an action or an argument for an action

            Take some time and add in a few of those here are some to get you started
                defmodule Tunez.Music.Artist do
                    use Ash.Resource, ...
                    resource do
                        description "A person or group of people that makes and releases music."
                    end

                    read :search do
                        description "List Artists, optionally filtering by name."
                        argument :query, :ci_string do
                            description "Return only artists with names including the given value."
                            # ...

        "Updating the API Title and Version"
            Head to lib/tunez_web/ash_json_api_router.ex and then add some lines so that when you are using the route there is a title and version in the heading.
                defmodule TunezWeb.AshJsonApiRouter do
                    use AshJsonApi.Router,
                        domains: [Tunez.Music],
                        open_api: "/open_api",
                        open_api_title: "Tunez API Documentation",
                        open_api_version: to_string(Application.spec(:tunez, :vsn))
                end

        "Removing Unwanted Extras"
            If you took some time to look through the API docs you see that there is some filtering that we are not using as we have our own setup. You can disable the built in versions if you want and it is with the derive_filter? boolean flag. Head to tunez/music/artist.ex and add this line.
                json_api do
                    type("artist")
                    includes([:albums])
                    derive_filter?(false)
                end

Building a GraphQL interface
    This is an other way to do the same things but this will do a few different things. We will go over those in the next section.

    Setup
        Using a similar igniter install we will be doing some different things:
            Code formatting and configuration for AshGraphql and Absinthe
            New graphql pipeline and scope for your Phoenix router, taking /gql and /gqp/playground
            New TunezWeb.GraphqlSchema
            New TunezWe.GraphqlSocket
        You can head to http://localhost:4000/gql/playground after we get it all setup.
            mix igniter.install ash_graphql

        This next bit will go fast as there is a lot that we have done that will look very similar

    Adding Artists to the API
        mix ash.extend Tunez.Music.Artist graphql

        We will now need to add in the queries and actions for the new API route. Head to lib/tunez/music.ex and add this new block.
            graphql do
                queries do
                    get Tunez.Music.Artist, :get_artist_by_id, :read
                    list Tunez.Music.Artist, :search_artists, :search
                end
            end

        This will create queries named getArtistById and searchArtistById that connects to the read action. We can now now add some non read options that will all be mutations, same file
            graphql do
                # ...

                mutations do
                create Tunez.Music.Artist, :create_artist, :create
                update Tunez.Music.Artist, :update_artist, :update
                delete Tunez.Music.Artist, :destroy_artist, :destroy
                end
            end

        "What Data Gets Included in API Responses?"
            We already did this in the previous section so this will not change anything

        "Creating Artist Records"
            Same as before this will not change.

    Adding Albums to the API
        We will use the same ash.extend but for graphql and albums then we will need to set the mutations for the graphql
            mix ash.extend Tunez.Music.Album graphql

        Then head to lib/tunez/music.ex
            graphql do
                mutations do
                    # ...    

                    create Tunez.Music.Album, :create_album, :create
                    update Tunez.Music.Album, :update_album, :update
                    destroy Tunez.Music.Album, :destroy_album, :destroy
                end
            end

        "Showing Albums for a Given Artist"
            Same as we did for the RESTful

    Customizing the Generated API
        Same as we did before we want to remove the unwanted fields it will be very similar but I will go over the needs for the graphql part

        "Removing Unwanted Extras"
            Here is where we will set the fields that will allows us to filter results, head to lib/tunez/music/artist.ex make these changes.
                graphql do
                    type :artist
                    filterable_fields [:album_count, :cover_image_url, :inserted_at, :latest_album_year_released, :updated_at]
                end

That is it for this section hope you learned something.