Prerequisites
Run app
cd
into the09_goals/
dir:cd 09_goals/
Fire up R:
R
Restore package dependencies:
::restore() renv
Once done, exit R.
server.R
is the entry point. To start the app, run this on the terminal:Rscript server.R
Explanation
This app starts a server and listens on port 5000 for connections.
In this example, we build a CRUD application backend: Goals.
Here are the defined routes:
/api
:/users
:- POST
/
: Register new user - POST
/login
: Login user - GET
/me
: Get user data - PUT
/me
: Update user details - DELETE
/me
: Delete user account
- POST
/goals
:- GET
/
: Get all user goals - POST
/
: Create a goal - PUT
/:id
: Update a goal - DELETE
/:id
: Delete a goal
- GET
You will be able to Create, Read, Update & Delete Goals.
Here’s what’s covered:
- Ambiorix + MongoDB
- Working with middleware:
- Auth middleware: You will learn how you can use JSON Web Tokens (JWT) to protect routes
- Error handling middleware
Requests to the API
Let’s explore how you can send requests to the API. We’ll do so from another R session.
Be sure to import the required functions first:
::use(
box
httr2[
request,
req_method,
req_perform,
req_url_path,
last_response,
resp_body_json,
req_body_multipart,
req_auth_bearer_token,
] )
/api/users*
Since the API requires JWT auth, you first need to create an account. To do that, make a POST request to /api/users
:
<- "http://127.0.0.1:5000"
base_url
# registration details:
<- list(
user_details name = "mwavu",
email = "mwavu@mail.com",
password = "test123"
)
<- request(base_url = base_url) |>
req req_url_path("/api/users") |>
req_body_multipart(!!!user_details)
# use `tryCatch()` in case an error occurs while performing the request:
<- tryCatch(
res expr = req |>
req_perform() |>
resp_body_json(),
error = \(e) {
print("An error occurred!")
<- last_response() |> resp_body_json()
error print(error)
} )
If that’s successful, you get back a named list of 3:
code
msg
user
:_id
: User idname
email
token
: A JWT token
Here is an example:
str(res)
# List of 3
# $ code: int 201
# $ msg : chr "Success."
# $ user:List of 4
# ..$ _id : chr "669ecec3f555b0571b09a3e1"
# ..$ name : chr "mwavu"
# ..$ email: chr "mwavu@mail.com"
# ..$ token: chr "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjQzNjIwNTIsImlhdCI6MTcyMTY4MzY1MiwidXNlcl9pZCI6IjY2OWVjZWMzZj"| __truncated__
To login a user, we make a POST request to /api/users/login
:
<- "http://127.0.0.1:5000"
base_url
# login details:
<- list(
user_details email = "mwavu@mail.com",
password = "test123"
)
<- request(base_url = base_url) |>
req req_url_path("/api/users/login") |>
req_body_multipart(!!!user_details)
# use `tryCatch()` in case an error occurs while performing the request:
<- tryCatch(
res expr = req |>
req_perform() |>
resp_body_json(),
error = \(e) {
print("An error occurred!")
<- last_response() |> resp_body_json()
error print(error)
} )
Again, on successful login, you get back a named list of 3:
code
msg
user
:_id
name
email
token
: A JWT token
str(res)
# List of 3
# $ code: int 200
# $ msg : chr "Success."
# $ user:List of 4
# ..$ _id : chr "669ecec3f555b0571b09a3e1"
# ..$ name : chr "mwavu"
# ..$ email: chr "mwavu@mail.com"
# ..$ token: chr "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjQzNjIzMjksImlhdCI6MTcyMTY4MzkyOSwidXNlcl9pZCI6IjY2OWVjZWMzZj"| __truncated__
To get details of a specific user, you need the JWT token returned during register or login. The token is verified by the auth middleware.
Make a GET request to /api/users/me
and include the JWT as an auth bearer token:
<- "http://127.0.0.1:5000"
base_url
# the JWT token from signup/login:
<- "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjQzNjIzMjksImlhdCI6MTcyMTY4MzkyOSwidXNlcl9pZCI6IjY2OWVjZWMzZjU1NWIwNTcxYjA5YTNlMSJ9.pzuhMl49qoXRKrUyLvTNHNRkO9bxMgSZd8dEeJR-adM"
token
<- request(base_url = base_url) |>
req req_url_path("/api/users/me") |>
req_auth_bearer_token(token = token)
# use `tryCatch()` in case an error occurs while performing the request:
<- tryCatch(
res expr = req |>
req_perform() |>
resp_body_json(),
error = \(e) {
print("An error occurred!")
<- last_response() |> resp_body_json()
error print(error)
} )
If successful, that returns list containing a named list of 3:
code
msg
user
:_id
name
email
For example:
str(res)
# List of 3
# $ code: int 200
# $ msg : chr "Success."
# $ user:List of 3
# ..$ _id : chr "669ecec3f555b0571b09a3e1"
# ..$ name : chr "mwavu"
# ..$ email: chr "mwavu@mail.com"
To update user details (name, email, password), send a PUT request to /api/users/me
with the new details in the body of the request:
<- "http://127.0.0.1:5000"
base_url
# the JWT token from signup/login:
<- "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjQyNjE1MDUsImlhdCI6MTcyMTU4MzEwNSwidXNlcl9pZCI6IjY2OWQ0NjAxYzdhZjI5MmMxNjA4YzIzMSJ9.C9ooaUJz-eZWJ69SSpbjGQXc1Mprd9hLm58vt3z6Ons"
token
# you can update the name, email or password:
<- list(
new_details name = "mwavukennedy",
password = "test1234"
)
<- request(base_url = base_url) |>
req req_url_path("/api/users/me") |>
req_auth_bearer_token(token = token) |>
req_method(method = "PUT") |>
req_body_multipart(!!!new_details)
# use `tryCatch()` in case an error occurs while performing the request:
<- tryCatch(
res expr = req |>
req_perform() |>
resp_body_json(),
error = \(e) {
print("An error occurred!")
<- last_response() |> resp_body_json()
error print(error)
} )
If successful, you should get back details of the updated user:
str(res)
# List of 3
# $ code: int 200
# $ msg : chr "Updated successfully!"
# $ user:List of 1
# ..$ :List of 3
# .. ..$ _id : chr "669ecec3f555b0571b09a3e1"
# .. ..$ name : chr "mwavukennedy"
# .. ..$ email: chr "mwavu@mail.com"
If you change the email/password and try logging in using the old credentials, note that the login attempt will fail.
To delete a user account, send a DELETE request to /api/users/me
:
<- "http://127.0.0.1:5000"
base_url
# the JWT token from signup/login:
<- "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjQyNjE1MDUsImlhdCI6MTcyMTU4MzEwNSwidXNlcl9pZCI6IjY2OWQ0NjAxYzdhZjI5MmMxNjA4YzIzMSJ9.C9ooaUJz-eZWJ69SSpbjGQXc1Mprd9hLm58vt3z6Ons"
token
<- request(base_url = base_url) |>
req req_url_path("/api/users/me") |>
req_auth_bearer_token(token = token) |>
req_method(method = "DELETE")
# use `tryCatch()` in case an error occurs while performing the request:
<- tryCatch(
res expr = req |>
req_perform() |>
resp_body_json(),
error = \(e) {
print("An error occurred!")
<- last_response() |> resp_body_json()
error print(error)
} )
If successful, you get back a 200 and a message:
str(res)
# List of 2
# $ code: int 200
# $ msg : chr "Account deleted"
/api/goals*
Every route in /api/goals*
is protected, meaning they can only be accessed by an authenticated user. Also, each user only has access to the goals they set, not anyone elses.
In other words, send the JWT as an auth bearer token in your requests.
Let’s set a goal by sending a POST request to /api/goals
:
<- "http://127.0.0.1:5000"
base_url
# the JWT token from signup/login:
<- "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjQyMDY5NDcsImlhdCI6MTcyMTUyODU0NywidXNlcl9pZCI6IjY2OWM2ZWU0ZGRmYTdiZTZhMTBmMDdlMSJ9.rpSOL0LynYm2BBP60Ikpz-GNIY6mR_ZKKzH9Tai2IS4"
token
# your goal:
<- "Learn Rust"
text
<- request(base_url = base_url) |>
req req_url_path("/api/goals") |>
req_auth_bearer_token(token = token) |>
req_body_multipart(text = text)
# use `tryCatch()` in case an error occurs while performing the request:
<- tryCatch(
res expr = req |>
req_perform() |>
resp_body_json(),
error = \(e) {
print("An error occurred!")
<- last_response() |> resp_body_json()
error print(error)
} )
If successful, that should return a list of 3:
code
msg
goal
:_id
: goal iduser_id
: user idtext
: the goal
For example:
str(res)
# List of 3
# $ code: int 201
# $ msg : chr "Success."
# $ goal:List of 3
# ..$ _id : chr "669ed1ce24f7bd52b80c6e92"
# ..$ user_id: chr "669ed19924f7bd52b80c6e91"
# ..$ text : chr "Learn Rust"
To get all goals a user has set, send a GET request to /api/goals
:
<- "http://127.0.0.1:5000"
base_url
# the JWT token from signup/login:
<- "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjQyMDY5NDcsImlhdCI6MTcyMTUyODU0NywidXNlcl9pZCI6IjY2OWM2ZWU0ZGRmYTdiZTZhMTBmMDdlMSJ9.rpSOL0LynYm2BBP60Ikpz-GNIY6mR_ZKKzH9Tai2IS4"
token
<- request(base_url = base_url) |>
req req_url_path("/api/goals") |>
req_auth_bearer_token(token = token)
# use `tryCatch()` in case an error occurs while performing the request:
<- tryCatch(
res expr = req |>
req_perform() |>
resp_body_json(),
error = \(e) {
print("An error occurred!")
<- last_response() |> resp_body_json()
error print(error)
} )
If successful, that should return a list of named lists. Each of the nested lists has 2 items:
_id
: Id of the goaltext
: The goal
For example:
str(res)
# List of 1
# $ goals:List of 3
# ..$ :List of 2
# .. ..$ _id : chr "669ed1ce24f7bd52b80c6e92"
# .. ..$ text: chr "Learn Rust"
# ..$ :List of 2
# .. ..$ _id : chr "669ed27324f7bd52b80c6e93"
# .. ..$ text: chr "Call Mum"
# ..$ :List of 2
# .. ..$ _id : chr "669ed27c24f7bd52b80c6e94"
# .. ..$ text: chr "Visit Aunt"
To update a goal, send a PUT request to /api/users/:id
:
<- "http://127.0.0.1:5000"
base_url
# the JWT token from signup/login:
<- "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjQyMDY5NDcsImlhdCI6MTcyMTUyODU0NywidXNlcl9pZCI6IjY2OWM2ZWU0ZGRmYTdiZTZhMTBmMDdlMSJ9.rpSOL0LynYm2BBP60Ikpz-GNIY6mR_ZKKzH9Tai2IS4"
token
<- "Learn Rust & Postgres"
updated_goal
<- request(base_url = base_url) |>
req req_url_path("/api/goals/669ed1ce24f7bd52b80c6e92") |>
req_auth_bearer_token(token = token) |>
req_method(method = "PUT") |>
req_body_multipart(text = updated_goal)
# use `tryCatch()` in case an error occurs while performing the request:
<- tryCatch(
res expr = req |>
req_perform() |>
resp_body_json(),
error = \(e) {
print("An error occurred!")
<- last_response() |> resp_body_json()
error print(error)
} )
If successful, you should get back a named list of 3:
code
msg
goal
:_id
text
str(res)
# List of 3
# $ code: int 200
# $ msg : chr "Goal updated successfully"
# $ goal:List of 2
# ..$ _id : chr "669ed1ce24f7bd52b80c6e92"
# ..$ text: chr "Learn Rust & Postgres"
To delete a goal, send a, well, DELETE request to /api/goals/:id
:
<- "http://127.0.0.1:5000"
base_url
# the JWT token from signup/login:
<- "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjQyMDY5NDcsImlhdCI6MTcyMTUyODU0NywidXNlcl9pZCI6IjY2OWM2ZWU0ZGRmYTdiZTZhMTBmMDdlMSJ9.rpSOL0LynYm2BBP60Ikpz-GNIY6mR_ZKKzH9Tai2IS4"
token
<- request(base_url = base_url) |>
req req_url_path("/api/goals/669ed1ce24f7bd52b80c6e92") |>
req_auth_bearer_token(token = token) |>
req_method(method = "DELETE")
# use `tryCatch()` in case an error occurs while performing the request:
<- tryCatch(
res expr = req |>
req_perform() |>
resp_body_json(),
error = \(e) {
print("An error occurred!")
<- last_response() |> resp_body_json()
error print(error)
} )
If successful, you will again get back a named list of 3:
code
msg
goal
:_id
text
str(res)
# List of 3
# $ code: int 200
# $ msg : chr "Goal deleted successfully"
# $ goal:List of 2
# ..$ _id : chr "669ed1ce24f7bd52b80c6e92"
# ..$ text: chr "Learn Rust & Postgres"
Live reloading
See how you can enable ✨live reloading✨.