7.8

Deleting a Post

In this section, we’ll learn how to delete a post by sending a DELETE HTTP request to our local json-server. Luckily, deleting a post takes much less effort than updating it.

Adding Delete Buttons

The first thing we need to do is add a delete button next to each row in ListPosts.elm.

viewPost : Post -> Html Msg
viewPost post =
        .
        .
        , td []
            [ a [ href postPath ] [ text "Edit" ] ]
        , td []
            [ button [ type_ "button", onClick (DeletePost post.id) ]
                [ text "Delete" ]
            ]
        ]

When the Delete button is clicked, it sends the DeletePost message to the Elm Runtime. Let’s add that message to the Msg type in ListPosts.elm.

type Msg
    .
    .
    | PostsReceived (WebData (List Post))
    | DeletePost PostId

Add a new branch to the update function in ListPosts.elm to handle the DeletePost message.

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        PostsReceived response ->
            ...

        DeletePost postId ->
            ( model, deletePost postId )

Creating Delete HTTP Request

The DeletePost postId -> branch delegates all work to the deletePost function. Let’s add that below update in ListPosts.elm.

deletePost : PostId -> Cmd Msg
deletePost postId =
    Http.request
        { method = "DELETE"
        , headers = []
        , url = "http://localhost:5019/posts/" ++ Post.idToString postId
        , body = Http.emptyBody
        , expect = Http.expectString PostDeleted
        , timeout = Nothing
        , tracker = Nothing
        }

Once again, we’re using the low-level function Http.request to create a delete request. To delete a resource on the server, we’re required to use the DELETE method. The url for deleting and updating a resource is exactly the same. The only information a server needs to delete a resource is its URL. That’s why we’ve assigned Http.emptyBody to the body field.

json-server responds with {} — an empty JSON object — when asked to delete a resource. Since there’s nothing to decode inside an empty JSON object, we passed Http.expectString instead of Http.expectJson to the expect field. Next we need to add PostDeleted to the Msg type in ListPosts.elm.

type Msg
    .
    .
    | DeletePost PostId
    | PostDeleted (Result Http.Error String)

Just like the PostSaved message from EditPost.elm, the payload for PostDeleted doesn’t need to be of type WebData. We also aren’t interested in tracking all the states our DELETE request goes through. All we need to know is whether the request is successful or not. The Result type is perfect for that. Let’s handle PostDeleted by adding two new branches to update in ListPosts.elm.

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        .
        .
        DeletePost postId ->
            ...

        PostDeleted (Ok _) ->
            ( model, fetchPosts )

        PostDeleted (Err error) ->
            ( { model | deleteError = Just (buildErrorMessage error) }
            , Cmd.none
            )

After a post is deleted, we want to refresh the list by re-fetching remaining posts. That’s why we’re returning a command to fetch posts from the PostDeleted (Ok response) -> branch. However, if we encounter an error while deleting a post we need to save the error message in deleteError. Let’s add that field to our model in ListPosts.elm.

type alias Model =
    { posts : WebData (List Post)
    , deleteError : Maybe String
    }

We also need to initialize it to Nothing in init. While we’re at it, let’s extract the code for initializing the model out to a separate function.

init : ( Model, Cmd Msg )
init =
    ( initialModel, fetchPosts )


initialModel : Model
initialModel =
    { posts = RemoteData.Loading
    , deleteError = Nothing
    }

Displaying Delete Error

The only thing remaining is to display a delete error. Add the following code to the bottom of ListPosts.elm.

viewDeleteError : Maybe String -> Html msg
viewDeleteError maybeError =
    case maybeError of
        Just error ->
            div []
                [ h3 [] [ text "Couldn't delete post at this time." ]
                , text ("Error: " ++ error)
                ]

        Nothing ->
            text ""

And call viewDeleteError from the view function in ListPosts.elm.

view : Model -> Html Msg
view model =
        .
        .
        , viewPosts model.posts
        , viewDeleteError model.deleteError
        ]

Testing

We’re now ready to test the delete functionality. Run json-server and elm-live from the beginning-elm directory in separate terminal windows if they aren’t running already.

$ json-server --watch server/db.json -p 5019

$ elm-live post-app/Main.elm --pushstate

Check the elm-live window in terminal to make sure everything compiled successfully. Now go to http://localhost:8000 and you should see a delete button on each row.

Click the delete button in first row and that post should disappear from the page.

Its JSON representation should also disappear from the server/db.json file.

{
  "posts": [
    {
      "id": 2,
      "title": "http-server",
      "authorName": "indexzero",
      "authorUrl": "https://github.com/indexzero"
    }
  ],
  "comments": [],
  "profile": {
    "name": "typicode"
  }
}

Additionally, json-server deleted the following comment that was associated with the first post. Notice how the comment below uses postId field to tie itself to the post we just deleted.

{
  "id": 1,
  "body": "some comment",
  "postId": 1
}

This is a common behavior on the server-side. When a resource is deleted, it’s likely that other dependent resources also get deleted.

Summary

In this section, we learned how to delete a post using the DELETE HTTP method. Compared to other operations we have covered so far — fetching and updating — deleting a post is relatively easy. We don’t need to encode or decode any JSON value. All we need to do is specify the location of the resource. In the next section, we’ll build a separate page for creating a new post.

Back to top
Close