Creando un sencillo API REST con Cabal

En está ocasión vamos a crear un sencillo servicio API Rest con Cabal.

Consumiremos un sencillo JSON como este:

[
{"autor":"Thomas Muller","id":"1000","precio":"299.0","titulo":"Sangre roja"},
{"autor":"Mariana Seller","id":"1001","precio":"309.0","titulo":"Tierra Hueca"}
]

Requerimientos:

  • Tener instalado Haskell. 
  • Tener instalado Cabal.
  • Instalar las dependencias: aeson, text, wai, warp, servant, servant-server.

Creando el servicio API con Cabal

Veremos algo sobre las dependencias necesarias.

Aeson (análisis y codificación rápidos de JSON)

Se trata de una biblioteca de análisis y codificación JSON optimizada para ofrecer facilidad de uso y un alto rendimiento.

Servant

Servant es un conjunto de paquetes para declarar APIs web a nivel de tipos y utilizar dichas especificaciones de API para:

  • Escribir servidores.
  • Obtener funciones cliente (en Haskell).
  • Generar funciones cliente para otros lenguajes de programación.
  • Generar documentación para tus aplicaciones web.
  • Etc.

Warp

Un servidor web rápido y ligero para aplicaciones WAI.

Wai (Web Application Interface)

Proporciona un protocolo común para la comunicación entre aplicaciones web y servidores web.

Text

Una implementación de texto Unicode eficiente en cuanto a tiempo y espacio.

Creando el proyecto con Cabal

1. Crearemos un directorio y nos ubicaremos en el:

$ mkdir hola-mundo
$ cd hola-mundo

2. Crearemos el proyecto con Cabal:

$ cabal init

Esto creará la estructura del proyecto:

hola-mundo/
   app/
     Main.hs
   CHANGELOG.md
   dist-newstyle
   hola-mundo.cabal
   LICENSE

Los archivos que modificaremos serán:

  • app/Main.hs 
  • hola-mundo.cabal

3. Modificaremos el archivo hola-mundo.cabal para agregar las siguientes dependencias:

build-depends: 
        base >=4.18 && <4.20,
        aeson ^>= 2.2,
        text ^>= 2.0,
        wai ^>= 3.2,
        warp ^>= 3.3,
        servant ^>= 0.20,
        servant-server ^>= 0.20

4. Ahora modificaremos el programa app/Main.hs. Comenzaremos agregando las extensiones:

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeOperators #-}

Donde:

  • DataKinds: permite promover valores a tipos (útil en Servant para rutas). 
  • DeriveGeneric: habilita la derivación automática de la clase Generic, necesaria para serialización JSON. 
  • OverloadedStrings: permite usar literales de texto ("hola") como Text sin necesidad de pack. 
  • TypeOperators: habilita operadores de tipos como :> usados en Servant.

Modificaremos el módulo:

module Main (main) where

Agregaremos las importaciones:

import Data.Aeson (ToJSON)
import Data.Text (Text)
import GHC.Generics (Generic)
import Network.Wai.Handler.Warp (run)
import Servant

Esto para:

  • Data.Aeson: para convertir tipos Haskell a JSON. 
  • Data.Text: tipo eficiente para manejar texto. 
  • GHC.Generics: soporte para derivar instancias automáticamente. 
  • Warp: servidor HTTP rápido. 
  • Servant: framework para definir APIs de manera declarativa.

Agregaremos la definición del objeto Libro, en Haskell se denominan records:

data Libro = Libro
  { id     :: Text
  , titulo :: Text
  , autor  :: Text
  , precio :: Text
  } deriving (Eq, Show, Generic)

La instancia del JSON:

instance ToJSON Libro

Definimos la lista del Libro:

libros :: [Libro]
libros =
  [ Libro "1000" "Sangre roja" "Thomas Muller" "299.0"
  , Libro "1001" "Tierra Hueca" "Mariana Seller" "309.0"
  ]

Definimos la API:

type API =
    "api" :> "libros" :> Get '[JSON] [Libro]

Definimos el Proxy:

api :: Proxy API
api = Proxy

Definimos el Servidor:

server :: Server API
server = return libros

Definimos la aplicación (App):

app :: Application
app = serve api server

Programa completo:

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeOperators #-}

module Main  (main) where

import Data.Aeson(ToJSON)
import Data.Text(Text)
import GHC.Generics(Generic)
import Network.Wai.Handler.Warp(run)
import Servant

data Libro = Libro{
    id::Text,
    titulo::Text,
    autor::Text,
    precio::Text
} deriving (Eq,Show,Generic)

instance ToJSON Libro

libros :: [Libro]
libros =
  [ Libro "1000" "Sangre roja" "Thomas Muller" "299.0"
  , Libro "1001" "Tierra Hueca" "Mariana Seller" "309.0"
  ]

type API = 
    "api":> "libros" :> Get '[JSON] [Libro]

api :: Proxy API 
api = Proxy

server :: Server API
server = return libros

app :: Application
app = serve api server


main :: IO ()
main = do
  putStrLn "Servidor escuchando en http://localhost:8080"
  run 8080 app

Ejecutamos la aplicación:

$ cabal update
$ cabal run

Si todo va bien, podremos consumir la API con un CURL:

$ curl -X GET http://localhost:8080/api/libros

Obtendremos lo siguiente:

[{"autor":"Thomas Muller","id":"1000","precio":"299.0","titulo":"Sangre roja"},
{"autor":"Mariana Seller","id":"1001","precio":"309.0","titulo":"Tierra Hueca"}
]

¡Hemos visto como crear una aplicación Cabal y Servant!

Continuaremos más sobre el tema en próximas entregas.

Enlaces:

https://onlyhaskellforyou.blogspot.com/2026/02/cabal-en-otro-vistazo.html
https://www.servant.dev/
https://www.haskell.org/cabal/
https://hackage.haskell.org/

Comentarios

Entradas populares de este blog

Cabal

Lenguajes similares Haskell: Agda

Tipos de datos en Haskell