Building an Animal Crossing New Horizons GraphQL API in Elixir

Ricardo Trindade
3 min readMay 11, 2020

--

Edit: I’ve added a couple of tests to the graphQL API, check the new article on my website https://www.ricardo-trindade.com/

Tons of people are playing Animal Crossing New Horizons during this period of quarantine (myself included). You may spend a lot of time fishing around your island, whether it is to donate your catches to Blathers or to score some bells. As you may know, some fishes only show up during certain months, on in certain times of day, and there’s plenty of resources out there that tell you where to go to get each fish. To strengthen my Elixir skills, I thought of giving a go at GraphQL in Elixir and create a simple endpoint, that allows you to query for the location of each fish and how much they’re worth (will probably add more functionality later on). The code for the project can be found on Github: https://github.com/RicardoTrindade/NewHorizons-Api

At the time of writing I was using Elixir 1.10.3 and Phoenix 1.5.1.

Creating a Phoenix project

So let’s get started and create a new Phoenix project.

mix phx.new newhorizonsapi --no-dashboard --no-html --no-webpack

The whole setup of the project is very similar to the existing tutorial in https://www.howtographql.com/graphql-elixir/0-introduction/. Although the router configuration is slightly different since we don’t have any HTML, CSS or Javascript in the project.

defmodule NewhorizonsapiWeb.Router do
use Phoenix.Router
forward "/api", Absinthe.Plug, schema: NewhorizonsapiWeb.Schema pipeline :api do
plug :accepts, ["json"]
end
end

The router is exposing the GraphQL endpoint under /api, that we’ll be able to reach using an application like Insomnia.
I’ve also created the Fish model using:

mix phx.gen.context Animals Fish fishes name:string price:integer location:string

Seeding the database

Finally to populate the database, I’ve imported the existing fish data from a CSV file, using the CSV lib by adding the following to seeds.exs

File.stream!(“fish_export.csv”)
|> Stream.drop(1)
|> CSV.decode(headers: [:name, :sell_value, :location])
|> Enum.each(fn {:ok, map} ->
Fish.changeset(
%Fish{},
%{name: map[:name], price: String.to_integer(map[:sell_value]), location: map[:location]}
)
|> Repo.insert!()
end)

Adding queries and resolvers

Now that the setup is complete, we can move on to the queries and corresponding resolvers. I’ll be adding 4 possible queries:

  • Retrieving all fishes;
  • Find a fish by its name;
  • Find a fish with a price higher than a certain amount;
  • Given a location, retrieve all fishes that you can catch there;

To achieve this I’ve created the fish_resolver module.

defmodule NewhorizonsapiWeb.FishResolver do
alias Newhorizonsapi.Animals
def all_fishes(_root, _args, _info) do
fishes = Animals.list_fishes()
{:ok, fishes}
end
def fish_search(_root, %{name: name}, _info) do
fish = Animals.get_fish_by_name(name)
{:ok, fish}
end
def fish_by_value(_root, %{price: price}, _info) do
fishes = Animals.get_fish_over_price(price)
{:ok, fishes}
end
def fish_by_location(_root, %{location: location}, _info) do
fishes = Animals.get_fish_by_location(location)
{:ok, fishes}
end
end

The code for getting all fishes is already implemented (given that you generated the Fish model via CLI) As for the code for the three remaining queries are as follows:

def get_fish_by_name(name), do: Repo.get_by!(Fish, name: name)def get_fish_over_price(price) do
query =
from(
fish in Fish,
where: fish.price >= ^price
)
query
|> Repo.all()
end
def get_fish_by_location(location) do
query =
from(
fish in Fish,
where: ilike(fish.location, ^"%#{location}%")
)
query
|> Repo.all()
end

And I think that is it! Go ahead and start the server. If we query the server using an app like insomnia here’s how it might look like.

Retrieving fishes that you can catch in the pond

In the future, I’m thinking to include the months, and times of day that you can catch each fish, and would also like to extend the functionality to bugs, as well as the ability to list fossils. I will also add more tests other than the pre-existing ones.

--

--

Ricardo Trindade

Fullstack engineer at Marley Spoon. Keen interest in machine learning, Ruby and Kotlin