Deleting a movie
In this chapter, we’ll add our final CRUD endpoint so that a client can delete a specific movie from our system.
| Method | URL Pattern | Handler | Action |
|---|---|---|---|
| GET | /v1/healthcheck | healthcheckHandler | Show application information |
| POST | /v1/movies | createMovieHandler | Create a new movie |
| GET | /v1/movies/:id | showMovieHandler | Show the details of a specific movie |
| PUT | /v1/movies/:id | updateMovieHandler | Update the details of a specific movie |
| DELETE | /v1/movies/:id | deleteMovieHandler | Delete a specific movie |
Compared to the other endpoints in our API, the behavior that we want to implement here is quite straightforward.
- If a movie with the
idprovided in the URL exists in the database, we want to delete the corresponding record and return a success message to the client. - If the movie
iddoesn’t exist, we want to return a404 Not Foundresponse to the client.
The SQL query to delete the record in our database is also simple:
DELETE FROM movies WHERE id = $1
In this case the SQL query returns no rows, so it’s appropriate for us to use Go’s Exec() method to execute it.
One of the nice things about Exec() is that it returns a sql.Result value, which contains information about the number of rows that the query affected. In our scenario, this is really useful information.
- If the number of rows affected is
1, then we know that the movie existed in the table and has now been deleted… so we can send the client a success message. - Conversely, if the number of rows affected is
0we know that no movie with thatidexisted at the point we tried to delete it, and we can send the client a404 Not Foundresponse.
Adding the new endpoint
Let’s go ahead and update the Delete() method in our database model. Essentially, we want this to execute the SQL query above and return an ErrRecordNotFound error if the number of rows affected is 0. Like so:
package data ... func (m MovieModel) Delete(id int64) error { // Return an ErrRecordNotFound error if the movie ID is less than 1. if id < 1 { return ErrRecordNotFound } // Construct the SQL query to delete the record. query := ` DELETE FROM movies WHERE id = $1` // Execute the SQL query using the Exec() method, passing in the id variable as // the value for the placeholder parameter. The Exec() method returns a sql.Result // value. result, err := m.DB.Exec(query, id) if err != nil { return err } // Call the RowsAffected() method on the sql.Result value to get the number of rows // affected by the query. rowsAffected, err := result.RowsAffected() if err != nil { return err } // If no rows were affected, we know that the movies table didn't contain a record // with the provided ID at the moment we tried to delete it. In that case we // return an ErrRecordNotFound error. if rowsAffected == 0 { return ErrRecordNotFound } return nil }
Once that’s done, let’s head to our cmd/api/movies.go file and add a new deleteMovieHandler method. In this we need to read the movie ID from the request URL, call the Delete() method that we just made, and — based on the return value from Delete() — send the appropriate response to the client.
package main ... func (app *application) deleteMovieHandler(w http.ResponseWriter, r *http.Request) { // Extract the movie ID from the URL. id, err := app.readIDParam(r) if err != nil { app.notFoundResponse(w, r) return } // Delete the movie from the database, sending a 404 Not Found response to the // client if there isn't a matching record. err = app.models.Movies.Delete(id) if err != nil { switch { case errors.Is(err, data.ErrRecordNotFound): app.notFoundResponse(w, r) default: app.serverErrorResponse(w, r, err) } return } // Return a 200 OK status code along with a success message. err = app.writeJSON(w, http.StatusOK, envelope{"message": "movie successfully deleted"}, nil) if err != nil { app.serverErrorResponse(w, r, err) } }
Finally, we need to hook up the new route in our cmd/api/routes.go file:
package main ... func (app *application) routes() http.Handler { router := httprouter.New() router.NotFound = http.HandlerFunc(app.notFoundResponse) router.MethodNotAllowed = http.HandlerFunc(app.methodNotAllowedResponse) router.HandlerFunc(http.MethodGet, "/v1/healthcheck", app.healthcheckHandler) router.HandlerFunc(http.MethodPost, "/v1/movies", app.createMovieHandler) router.HandlerFunc(http.MethodGet, "/v1/movies/:id", app.showMovieHandler) router.HandlerFunc(http.MethodPut, "/v1/movies/:id", app.updateMovieHandler) // Add the route for the DELETE /v1/movies/:id endpoint. router.HandlerFunc(http.MethodDelete, "/v1/movies/:id", app.deleteMovieHandler) return app.recoverPanic(router) }
OK, let’s restart the API and try this out by deleting Deadpool from our movie database (this should have an ID of 3 if you’ve been following along). The delete operation should work without any problems, and you should receive the confirmation message like so:
$ curl -X DELETE localhost:4000/v1/movies/3
{
"message": "movie successfully deleted"
}
If you repeat the same request to delete the already-deleted movie, you should now get a 404 Not Found response and error message. Similar to this:
$ curl -X DELETE localhost:4000/v1/movies/3
{
"error": "the requested resource could not be found"
}