Go言語とMongoDBでスキーマレスなデータを管理するAPIをつくってみた。

## はじめに

こんにちは

必要に迫られてドキュメント型DBの１つであるMongoDBを触らなければならない機会があったのでその共有（今頃？ｗ）

Go言語を例にMongoDBで簡単なAPIをつくってみたいと思います。

## MongoDBとのコネクション

### ドライバーの導入

MongoDBは独自プロトコルを採用しているため、ドライバーを使う必要があります。

※今回はスター数からこちらのドライバーを使わせて頂くことにしました。
https://github.com/mongodb/mongo-go-driver

※導入方法のざっくりとした説明は前回の記事でまとめております。
https://qiita.com/leica19/items/ccdd2b452477b1ad7aed

### Go言語からの接続

必要なパッケージはこちら

```go:db/main.go
import (
    "context"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
)
```

実コードはこの２行で完結します。

```go:db/main.go
    clientOptions := options.Client().ApplyURI("mongodb://db")
    client, _ := mongo.Connect(context.TODO(), clientOptions)
```

ApplyURI()の引数にある「db」がhostとなりますが、今回はdocker-composeを利用しているため「db」という名前でアクセスすることができます。

- docker-compose.yml
    - https://github.com/leica19/go-mongo-sample-api/blob/master/docker-compose.yml

見栄えよく関数化すると

```go:db/main.go
func ConnectMongoDB() *mongo.Client {
	clientOptions := options.Client().ApplyURI("mongodb://db")
	client, err := mongo.Connect(context.TODO(), clientOptions)
	if err != nil {
		log.Fatal(err)
	}
	return client
}
```

こんな感じでしょうか。

## コレクションの作成

次にコレクションを作成する必要があります。
※コレクションとはドキュメントの集合単位です。

コレクションの作成にはmongo.ClientのCollectionメソッドが使えます。
戻り値としてmongo.Collectionのハンドラーが返ってきます。
※今後は主にこのハンドラーを使ってコレクションを操作していくことになります。

```go:db/main.go
    collection := client.Database("test").Collection("users")
```

今回は"users"という名前のコレクションを作成しています。

## ドキュメントの新規作成

POSTのエンドポイントを経由してusersコレクションに新規ドキュメントを登録してみます。
JSONをPOSTできるようにします。

以下、ルーティングとハンドラー

```go:main.go
    e.POST("/user", func(c echo.Context) error {
		jsonBody := echo.Map{}
		if err := c.Bind(&jsonBody); err != nil {
			errorResponse := ErrorResponse{Message: "Bad Request"}
			return c.JSON(http.StatusBadRequest, errorResponse)
		}
		result, _ := collection.InsertOne(context.TODO(), jsonBody)
		response := Response{ID: result.InsertedID}
		return c.JSON(http.StatusOK, response)
	})
```

リクエストのBodyパラメーターをc.Bind()でGo言語のmapにマッピングしております。
※Web frameworkとしてEchoを使っています。https://echo.labstack.com/guide
レコードの新規作成には、InsertOneメソッドを利用しています。

さっそく登録してみます。

![Screenshot from 2019-08-24 18-17-57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258195/ebcf5504-57a9-1fa0-cc6f-9cc9eb53946f.png)

レスポンスにはドキュメントのIDであるObjectIdを返しています。

## ドキュメントの参照

続いて、先ほど登録したドキュメントがきちんと登録されているかGETリクエストで確認してみます。

ハンドラーから

```go:main.go
    e.GET("/user/:_id", func(c echo.Context) error {
		raw_id := c.Param("_id")
		_id, _ := primitive.ObjectIDFromHex(raw_id)
		result := echo.Map{}
		if err := collection.FindOne(context.TODO(), bson.M{"_id": _id}).Decode(&result); err != nil {
			errorResponse := ErrorResponse{Message: "Not Found"}
			return c.JSON(http.StatusNotFound, errorResponse)
		}
		return c.JSON(http.StatusOK, result)
	})
```

ポイントは、primitiveパッケージのObjectIDFromHex関数を使って、
16進数の文字列できたObjectIdを正しいObjectIdに変換する処理を挟むところです。
※内部的なことはよくわかってません。すみません。

ObjectIdをリクエストパスに入れてGETすると

![Screenshot from 2019-08-24 18-18-11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258195/093506f3-6f0a-afd3-30d2-6601c741d441.png)

先ほどPOSTしたJSONが正しく返ってきています。

## ドキュメントの削除

最後に削除です。

特に目立ったところはありませんが、ハンドラーは

```go:main.go
	e.DELETE("/user/:_id", func(c echo.Context) error {
		raw_id := c.Param("_id")
		_id, _ := primitive.ObjectIDFromHex(raw_id)
		result, _ := collection.DeleteOne(context.TODO(), bson.M{"_id": _id})
		return c.JSON(http.StatusOK, result)
	})
```

仮実装なので、DeleteOneメソッドの戻り値をそのままレスポンスしています。。
リクエストパスにObjectIdを入れて、DELETEメソッドでリクエストすればドキュメントが削除されます。

![Screenshot from 2019-08-24 18-18-22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258195/6f6c0b8e-8e36-1af0-ef87-b7683476f869.png)

一応GETでも確認

![Screenshot from 2019-08-24 18-18-29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258195/a02ac1ff-5e9b-90f3-05a2-635b2cac00f2.png)

きちんとドキュメントが削除されています。

## 最後に

今回は非常にあっさりしたものですが、今後はmongoのクエリにも詳しくなりたいな。というかならなくちゃ。

読んで頂き、ありがとうございました。

今回のソースコードはこちらで公開しております。
https://github.com/leica19/go-mongo-sample-api

## 参考
- MongoDB公式チュートリアル
    - https://www.mongodb.com/blog/post/mongodb-go-driver-tutorial

- 公式APIドキュメント
    - https://godoc.org/go.mongodb.org/mongo-driver/mongo