El blog API de El Café de Félix (Primera Parte)

En el post anterior seteé un par de rutas bajo el umbrella app de mi café. La ruta “/blog” que apunta a mi aplicación de CMS no hace nada porque ni siquiera tiene un controlador para traducir el request.

Arreglemos esto ahoritititititititita. Pero antes, un par de aclaraciones:

  1. El CMS app sólo va a servir requests al API. O sea, cero HTML. Por ahora no quiero  distaerme hablando de UX, Javascript Frameworks, chatbots, etc. etc. De eso ya he hablado bastante en mi blog viejo.
  2. Voy a probar a usar GraphQL, en vez del “tradicional” REST. GraphQL me permite tener requests más complejos en menos operaciones. Hoy en día, una buena parte de la lentitud de las aplicaciones web es debido a que hay demasiados requests a los servidores. GraphQL aliviara esto. Y lo otro es que hay menos controladores que crear, lo que significa más fácil mantenimiento (soy un flojo, lol)
  3. Hablando de flojera, voy a seguir el blog de Ryan Swapp para guiarme con la parte de GraphQL, combinado con el modelo del blog de 15 minutos de monterail. Ambos blogs están en inglés. Si encuentro algo que no funciona y necesita cambiarse, lo mencionaré en el post.
  4. Si fuera menos “didáctico”, mi recomendación sería usar Jekyll o parecido para crear un blog sin tener que depender de un servidor de contenido (o sea que todos los posts son creados “físicamente”). Algún día quizá hable de como hacer páginas de Jekyll con Elixir o Ruby.

Ok. Comienzo entonces.

Paso 1: actualizando las dependencias

En el editor, abro /apps/cms/mix.exs y agrego las siguientes dependencias:

  • absinthe_plug y absinthe_ecto, para soporte a GraphQL. Absinthe_ecto aún está en desarrollo, así que hay que agregarle una referencia directa al repositorio de github.
  • poison, una de las librerías de Elixir más populares para manejo de JSON
  • faker, para crear data de prueba facilmente (aka. los lorem ipsums)

Como referencia, esto es como se vería en el mix.exs (sólo las partes que he cambiado):

...
 def application do
 [mod: {Cms, []},
 applications: [:phoenix, :phoenix_pubsub, :cowboy, :logger, :faker, :gettext,
 :phoenix_ecto, :postgrex]]
 end
...
  # Specifies your project dependencies.
  #
  # Type `mix help deps` for examples and options.
  defp deps do
    [{:phoenix, "~> 1.2.1"},
     {:phoenix_pubsub, "~> 1.0"},
     {:phoenix_ecto, "~> 3.0"},
     {:postgrex, ">= 0.0.0"},
     {:gettext, "~> 0.11"},
     {:cowboy, "~> 1.0"},
     {:absinthe_plug, "~> 1.1"},
     {:absinthe_ecto, git: "https://github.com/absinthe-graphql/absinthe_ecto.git"},
     {:poison, "~> 2.1.0"},
     {:faker, "~> 0.7"}]
  end
...

En el command prompt, actualizo las dependencias:

$ cd elcafedefelix/apps
$ cd cms
$ mix deps.get

Paso 2: creando las tablas

El blog va a ser muy sencillo: sólo va a tener Posts y Comentarios (por ahora al menos). Para crear los modelos para el blog (tablas en el PostgreSQL), lo más sencillo es usar mix. Siguiendo con el command prompt:

$ mix phoenix.gen.model Post posts title:string body:text
$ mix phoenix.gen.model Comment comments name:string content:text post_id:references:posts
$ mix ecto.migrate

En el editor, me aseguro de indicar que un Post puede tener muchos comentarios. Esto será muy útil cuando defina el esquema para el graphql. En /cms/web/models/post.ex:

schema "posts" do
    field :title, :string
    field :body, :string
    has_many :comments, Cms.Comment

    timestamps()
  end

Para no perder tiempo creando posts y comentarios, voy a usar faker. Modifico cms/priv/seeds.ex con lo siguiente:

alias Cms.Repo
alias Cms.Post
alias Cms.Comment

Repo.insert!(%Post{title: "Post 001", body: "Un post como otro"})
Repo.insert!(%Post{title: "Post 002", body: "Otro post parecido a los demas"})

for _ <- 1..10 do
 Repo.insert!(%Comment{
 name: Faker.Name.name,
 content: Faker.Lorem.paragraph,
 post_id: [1,2] |> Enum.take_random(1) |> hd
 })
end

Y en el command prompt ejecuto el seed que acabo de crear:

$ mix run priv/repo/seeds.exs

Esto generará comentarios con texto al azar. Super útil para tests.

Paso 3: El esquema del GraphQL

Primero, creo un par de folders bajo cms/web: uno para los types (cms/web/schema) y otro para los resolvers (cms/web/resolvers)

En el editor, creo un nuevo archivo que definirá mi esquema: cms/web/schema/schema.ex

defmodule Cms.Schema do
  use Absinthe.Schema
  import_types Cms.Schema.Types

  query do
    field :posts, list_of(:post) do
      resolve &Cms.PostResolver.all/2
    end

    field :comments, list_of(:comment) do
      resolve &Cms.CommentResolver.all/2
    end

    field :post, type: :post do
      arg :id, non_null(:id)
      resolve &Cms.PostResolver.find/2
    end
  end
end

Ahora tengo que definir los types y los resolvers. Creo los siguientes archivos:

Types (cms/web/schema/types.ex):

defmodule Cms.Schema.Types do
  use Absinthe.Schema.Notation
  use Absinthe.Ecto, repo: Cms.Repo

 object :post do
   field :id, :id
   field :title, :string
   field :body, :string
   field :comments, list_of(:comment), resolve: assoc(:comments)
 end

 object :comment do
   field :id, :id
   field :name, :string
   field :content, :string
   field :post, :post, resolve: assoc(:post)
 end

end

El resolver para los posts (cms/web/resolvers/post_resolver.ex)

defmodule Cms.PostResolver do
  alias Cms.Repo
  alias Cms.Post

  def all(_args, _info) do
   {:ok, Repo.all(Post)}
  end

  def find(%{id: id}, _info) do
    case Repo.get(Post, id) do
      nil -> {:error, "Post id #{id} not found"}
      post -> {:ok, post}
    end
  end
end

Y el resolver para los comentarios (cms/web/resolvers/comment_resolver.ex)

defmodule Cms.CommentResolver do
  alias Cms.Repo
  alias Cms.Comment

  def all(_args, _info) do
    {:ok, Repo.all(Comment)}
  end
end

Paso 4: el Router

Ya casi termino. Lo que falta es que el router del app del CMS apunte a Absinthe. En cms/web/router.ex

defmodule Cms.Router do
  use Cms.Web, :router

  pipeline :api do
    plug :accepts, ["json"]
  end

#  scope "/api", Cms do
#    pipe_through :api
#  end

  forward "/graphiql", Absinthe.Plug.GraphiQL,
    schema: Cms.Schema

  forward "/", Absinthe.Plug,
    schema: Cms.Schema

end

Paso 5: tests

Finalmente, podemos comprobar si esto funciona (y si de paso ya se arregló el problema con las rutas del blog anterior):

$ cd elcafedefelix
$ mix phoenix.server
[info] Running Website.Endpoint with Cowboy using http://localhost:4000

Probemos en un browser a abrir las rutas:

Ahora sí: Todas nuestras rutas funcionan. Como estoy usando graphql, la mejor forma de probar que el esquema está funcionando es usando graphiql, que es un mini-app que viene con el absinthe.

Abriendo: http://localhost:4000/blog/graphiql

Pongo en el URL: http://localhost:4000/blog/graphiql

y en el query uso un graphql como esto:

{
 posts {
   title,
   comments {
      name,
      content
   }
 }
}

Todo funcionó como se esperaba.

blogapi1

Ahora sí, a commitear el código y tomarse un cafecito.

Lo que aprendí hoy:

  • GraphQL con Absinthe
  • Cómo crear seeds con faker
  • A usar Graphiql

 

 

Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s