04: simple json api

Build a simple API

04_simple_json_api
Author

Kennedy Mwavu

Published

July 10, 2024

Run app

  1. cd into the 04_simple_json_api/ dir:

    cd 04_simple_json_api/
  2. Fire up R:

    R
  3. Restore package dependencies:

    renv::restore()

    Once done, exit R.

  4. server.R is the entry point. To start the app, run this on the terminal:

    Rscript index.R

Explanation

This app starts a server and listens on port 3000 for connections.

It has two endpoints:

  • /api/members
  • /api/members/:id

Ideally, the backend works hand-in-hand with the database. But for the sake of simplicity, let’s hardcode a list of members:

members <- data.frame(
  id = as.character(1:3),
  name = c("John Doe", "Bob Williams", "Shannon Jackson"),
  email = c("john@gmail.com", "bob@gmail.com", "shannon@gmail.com"),
  status = c("active", "inactive", "active")
)

You don’t have to worry about converting members into JSON because the res$json() method does it for you.

Create a GET request to get all members:

app$get("/api/members", \(req, res) {
  res$json(members)
})

GET /api/members

Create a GET request to get a single member by id:

app$get("/api/members/:id", \(req, res) {
  # get the supplied id:
  member_id <- req$params$id

  # filter member with that id:
  found <- members |> dplyr::filter(id == member_id)

  res$json(found)
})

GET /api/members/:id

You can also dictate how the response changes if no member is found:

# get a single member:
app$get("/api/members/:id", \(req, res) {
  # get the supplied id:
  member_id <- req$params$id

  # filter member with that id:
  found <- members |> dplyr::filter(id == member_id)

  # if a member with that id was found, return the member:
  if (nrow(found) > 0) {
    return(res$json(found))
  }

  # otherwise, change response status to 400 (Bad Request)
  # and provide a message:
  msg <- list(msg = sprintf("No member with the id of %s", member_id))
  res$set_status(400L)$json(msg)
})

GET /api/members/:id

A POST request to create a new member:

app$post("/api/members", \(req, res) {
  # parse form-data:
  body <- parse_multipart(req)

  # details of the new member:
  new_member <- data.frame(
    id = uuid::UUIDgenerate(),
    name = body$name,
    email = body$email,
    status = body$status
  )

  # save new member:
  members <<- dplyr::bind_rows(members, new_member)

  # respond with a message and details of the newly created member:
  response <- list(
    msg = "Member created successfully!",
    member = new_member
  )

  res$json(response)
})

POST /api/members

You can of course ensure all the details are sent (name, email & status) before creating the new member:

app$post("/api/members", \(req, res) {
  # parse form-data:
  body <- parse_multipart(req)

  name <- body$name
  email <- body$email
  status <- body$status

  # require all member details:
  if (is.null(name) || is.null(email) || is.null(status)) {
    msg <- list(msg = "Please include a name, email & status")
    return(res$set_status(400L)$json(msg))
  }

  # details of the new member:
  new_member <- data.frame(
    id = uuid::UUIDgenerate(),
    name = name,
    email = email,
    status = status
  )

  # save new member:
  members <<- dplyr::bind_rows(members, new_member)

  # respond with a message and details of the newly created member:
  response <- list(
    msg = "Member created successfully!",
    member = new_member
  )

  res$json(response)
})

POST /api/members

Create a PUT request to update a member:

app$put("/api/members/:id", \(req, res) {
  # get the supplied id:
  member_id <- req$params$id

  # filter member with that id:
  found <- members |> dplyr::filter(id == member_id)

  # if a member with that id is NOT found, change response status
  # and provide a message:
  if (nrow(found) == 0) {
    msg <- list(msg = sprintf("No member with the id of %s", member_id))
    return(res$set_status(400L)$json(msg))
  }

  # otherwise, proceed to update member:
  body <- parse_multipart(req)

  # only update provided fields:
  found$name <- body$name %||% found$name
  found$email <- body$email %||% found$email
  found$status <- body$status %||% found$status

  members[members$id == found$id, ] <- found

  response <- list(
    msg = "Member updated successfully",
    member = found
  )
  res$json(response)
})

PUT /api/members/:id

Create a delete request to, well, delete a member:

app$delete("/api/members/:id", \(req, res) {
  # get the supplied id:
  member_id <- req$params$id

  # filter member with that id:
  found <- members |> dplyr::filter(id == member_id)

  # if a member with that id is NOT found, change response status
  # and provide a message:
  if (nrow(found) == 0) {
    msg <- list(msg = sprintf("No member with the id of %s", member_id))
    return(res$set_status(400L)$json(msg))
  }

  # otherwise, proceed to delete member:
  members <<- members |> dplyr::filter(id != member_id)

  response <- list(
    msg = "Member deleted successfully",
    members = members
  )
  res$json(response)
})

DELETE /api/members/:id

Router

Things are getting out of hand in server.R. Take a look at ✨routers✨.