0
0

More than 1 year has passed since last update.

【Go】APIサーバの構築 ~データの作成(Create)/更新(Update)/削除(Delete)編~

Last updated at Posted at 2022-01-10

はじめに

前記事の続きでデータの作成、更新、削除の処理を追加しました。

実装

各処理の実装内容について記述します。

削除(Delete)

まずはroutes.goにルートハンドラーを追加します。

routes.go
router.HandlerFunc(http.MethodGet, "/v1/admin/deletemovie/:id", app.deleteMovie)

処理の内容をmovie-handlers.goに記述します。
パスパラメータを取得するとき、strconv.Atoi(params.ByName("id"))でidの型をStringからIntに変換することに注意します。

movie-handlers.go
func (app *application) deleteMovie(w http.ResponseWriter, r *http.Request) {
    params := httprouter.ParamsFromContext(r.Context())

    // パスパラメータのidをIntに変換する
    id, err := strconv.Atoi(params.ByName("id"))
    if err != nil {
        app.errorJSON(w, err)
        return
    }

    // データの削除処理を行う
    err = app.models.DB.DeleteMovie(id)
    if err != nil {
        app.errorJSON(w, err)
        return
    }

    ok := jsonResp{
        OK: true,
    }

    err = app.writeJSON(w, http.StatusOK, ok, "response")
    if err != nil {
        app.errorJSON(w, err)
        return
    }
}

DBデータの削除処理DeleteMovie()movies-db.goに記述します。
データの取得処理にはQueryContext()を使用していましたが、データを変更する処理(削除、更新など)を行う場合にはexecContext()を使用します。

movies-db.go
func (m *DBModel) DeleteMovie(id int) error {
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel()

    stmt := "delete from movies where id = $1"

    _, err := m.DB.ExecContext(ctx, stmt, id)
    if err != nil {
        return err
    }

    return nil
}

作成(Create)/更新(Update)

データの作成と更新については、既存のデータを編集するかどうかの違いだけで処理自体はほぼ同じです。
そのため、ルートハンドラーは一つにまとめてしまいます。

routes.go
router.HandlerFunc(http.MethodPost, "/v1/admin/editmovie", app.editMovie)

削除のときと同様に、処理の中身はmovie-handler.goに記述します。

まずはリクエストデータの型をMoviePayloadという構造体で定義します。
payloadという構造体を定義し、json.NewDecoder(r.Body).Decode(&payload)でリクエストデータの中身r.Bodypayloadに入れます。

リクエストデータのレコードをDBに登録(作成)するために、movie.ID, _ = strconv.Atoi(payload.ID)のように、payloadのプロパティをmovieテーブルのカラムと同じ型に変換していきます。
そして、movie.ID == 0のときにレコード作成処理InsertMovie()、それ以外のときに更新処理UpdateMovie()を行うようにします。

movie-handler.go
// JSONと同じ構造の構造体の型を定義する(入力値の型とDBのカラムの型は異なる)
type MoviePayload struct {
    ID string `json:"id"`
    Title string `json:"title"`
    Description string `json:"description"`
    Year string `json:"year"`
    ReleaseDate string `json:"release_date"`
    Runtime string `json:"runtime"`
    Rating string `json:"rating"`
    MPAARating string `json:"mpaa_rating"`
}

func (app *application) editMovie(w http.ResponseWriter, r *http.Request) {
    // リクエストデータの型をもつ構造体を定義する
    var payload MoviePayload

    // JSONオブジェクトを読み込んでpayloadに代入する
    err := json.NewDecoder(r.Body).Decode(&payload)
    if err != nil {
        log.Println(err)
        app.errorJSON(w, err)
        return
    }

    // DBデータの型をもつ構造体を定義する
    var movie models.Movie

    // データ更新時にUpdatedAtを更新する
    if payload.ID != "0" {
        id, _ := strconv.Atoi(payload.ID)
        m, _ := app.models.DB.Get(id)
        movie = *m
        movie.UpdatedAt = time.Now()
    }

    // payloadの各プロパティの型を変換してmovieに代入する
    movie.ID, _ = strconv.Atoi(payload.ID)
    movie.Title = payload.Title
    movie.Description = payload.Description
    movie.ReleaseDate, _ = time.Parse("2006-01-02", payload.ReleaseDate)
    movie.Year = movie.ReleaseDate.Year()
    movie.Runtime, _ = strconv.Atoi(payload.Runtime)
    movie.Rating, _ = strconv.Atoi(payload.Rating)
    movie.MPAARating = payload.MPAARating
    movie.CreatedAt = time.Now()
    movie.UpdatedAt = time.Now()

    if movie.ID == 0 { // データ作成時の処理
        err = app.models.DB.InsertMovie(movie)
        if err != nil {
            app.errorJSON(w, err)
            return
        }
    } else { // データ更新時の処理
        err = app.models.DB.UpdateMovie(movie)
        if err != nil {
            app.errorJSON(w, err)
            return
        }
    }

    ok := jsonResp{
        OK: true,
    }

    err = app.writeJSON(w, http.StatusOK, ok, "response")
    if err != nil {
        app.errorJSON(w, err)
        return
    }
}

各処理の中身は以下のようになります。

movie-db.go
func (m *DBModel) InsertMovie(movie Movie) error {
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel()

    stmt := `insert into movies (title, description, year, release_date, runtime, rating, mpaa_rating, created_at, updated_at) values ($1, $2, $3, $4, $5, $6, $7, $8, $9)`

    _, err := m.DB.ExecContext(ctx, stmt,
        movie.Title,
        movie.Description,
        movie.Year,
        movie.ReleaseDate,
        movie.Runtime,
        movie.Rating,
        movie.MPAARating,
        movie.CreatedAt,
        movie.UpdatedAt,
    )

    if err != nil {
        return err
    }

    return nil
}
movie-db.go
func (m *DBModel) UpdateMovie(movie Movie) error {
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel()

    stmt := `update movies set title = $1, description = $2, year = $3, release_date = $4, 
                        runtime = $5, rating = $6, mpaa_rating = $7, 
                        updated_at = $8 where id = $9`

    _, err := m.DB.ExecContext(ctx, stmt,
        movie.Title,
        movie.Description,
        movie.Year,
        movie.ReleaseDate,
        movie.Runtime,
        movie.Rating,
        movie.MPAARating,
        movie.UpdatedAt,
        movie.ID,
    )

    if err != nil {
        return err
    }

    return nil
}

コード全文

以下をご覧ください。

参考資料

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0