/todo-list-yesod

TODO-list backend application implemented with Yesod web framework

Primary LanguageHaskellBSD 3-Clause "New" or "Revised" LicenseBSD-3-Clause

TODO-list by Yesod

Intro

Here you may find TODO-list backend application implemented with Yesod, web framework for Haskell.

It has basic CRUD operations, responds with JSON only, uses Persistent as a storage interface, and additionally implements a simple custom logger middleware.

The main goal of this project is to provide a basic example of implementing backend with Yesod framework, so to make life a bit easier for (future) Haskell web developers.

This code complements the talk "How to Choose a Haskell Web Framework" I presented at Haskell eXchange 2022.

In this talk, I show how some of Haskell web tools approach web development. In particular, there are 3 of them — Servant, Yesod and IHP.

There are also corresponding Servant and IHP TODO-list implementations.

Check out the presentation slides and the talk recording, and contact me if you have any questions 🙂

API

As application has only basic CRUD operations, here is the API

  • GET /api/task — get all tasks
  • POST /api/task — create new task
  • GET /api/task/{id} — get existing task by id
  • PUT /api/task/{id} — update existing task by id
  • DELETE /api/task/{id} — delete existing task by id

Request & response examples

We use curl to demonstrate interaction with API

> curl -X GET http://localhost:3000/api/task
[]

> curl -X POST http://localhost:3000/api/task -H "Content-Type: application/json" -d '{"content": "wake up"}'
{"content":"wake up","id":1}

> curl -X POST http://localhost:3000/api/task -H "Content-Type: application/json" -d '{"content": "drink coffee"}'
{"content":"drink coffee","id":2}

> curl -X PUT http://localhost:3000/api/task/2 -H "Content-Type: application/json" -d '{"content": "drink mooore coffee"}'
{"content":"drink mooore coffee","id":2}

> curl -X GET http://localhost:3000/api/task
[{"content":"wake up","id":1},{"content":"drink mooore coffee","id":2}]

> curl -X GET http://localhost:3000/api/task/2
{"content":"drink mooore coffee","id":2}

> curl -X GET http://localhost:3000/api/task/5
<!DOCTYPE html>
<html><head><title>Not Found</title></head><body><h1>Not Found</h1>
<p>/api/task/5</p>
</body></html>

> curl -X DELETE http://localhost:3000/api/task/1
{"content":"wake up","id":1}

> curl -X GET http://localhost:3000/api/task
[{"content":"drink mooore coffee","id":2}]

* Note that requesting unexisting task returns HTML code. The reason is the usage of get404 function in handlers. This behaviour may be changed to return an empty JSON with 404 error code.

Custom logger middleware

Simple logger here just extracts request’s URL path and logs it with "Info" verbosity level.

For example,

[Info] url-path=api/task/5
[Info] url-path=api/task/
[Info] url-path=api/task
[Info] url-path=api/task/8/
[Info] url-path=api/task/3
[Info] url-path=/
[Info] url-path=api/task

Prerequisites

Run

Firstly, you need to configure a connection string to connect to your database. You may refer to the PostgreSQL docs for this.

In this example, the following connection string is used

connectionStr = "host=localhost dbname=todolist-yesod user=postgres password=postgres port=5432"

Which means that the local todolist-yesod postgres database is accessed via 5432 port.

Make sure that user and database exist before running the app.

Then, run the app

> stack run
Migrating: CREATe TABLE "task"("id" SERIAL8  PRIMARY KEY UNIQUE,"content" VARCHAR NOT NULL)
[Debug#SQL] CREATe TABLE "task"("id" SERIAL8  PRIMARY KEY UNIQUE,"content" VARCHAR NOT NULL); []
...

Hope you'll find it helpful 💙