Home Posts Tags Post Search Tag Search

Post 76

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.