Casual Developers Notes

初級・中級のエンジニアやデザイナー向けに技術情報と英語学習情報を提供中。エンジニアもデザイナーも技術と英語を身に着けて海外に飛び立とう!

  • ホーム
  • 技術 Tips & Tutorials
  • 技術塾
  • ライフハック
  • 海外留学
  • 英語学習
  • コラム
  • お問い合わせ

2019年2月15日 By Keid Leave a Comment

NPMモジュールを自動的にセマンティック・バージョニングで管理する方法(semantic-release編)

NPMモジュールを自動的にセマンティック・バージョニングで管理する方法(semantic-release編)

NPMモジュールを自動的にセマンティック・バージョニングで管理するためのモジュール「semantic-release」の導入方法を紹介します。

はじめに

NPMモジュールをバージョン管理する場合、セマンティック・バージョニングに従うのが一般的です。これを手動でやろうとすると、リリースの運用ルールを自分で決めて、NPMコマンドを使ってバージョンを上げてNPMに公開することになります。手動というのはミスも起きますし、そもそも面倒です。そこで、コミットメッセージのルールを決めることで、コミットするだけで自動的にセマンティック・バージョニングでバージョンを上げてNPMに公開してくれるモジュールが「semantic-release」です。今回は、「semantic-release」の導入方法を紹介します。

また、NPMモジュールを自作で作成する方法を知りたい人は以前の記事を参照してください。

セマンティック・バージョニングとは?

セマンティック・バージョニング(Semantic Versioning)とは、仕様が公開されており、ほとんどのNPMモジュールで採用されているバージョン管理の方法です。ざっくり言うと、バージョンを「X.Y.Z」とすると、「X」はMajor version(ブレイキングチェンジがある場合に上げる)、「Y」はMinor version(機能追加がある場合に上げる)、「Z」はPatch version(バグフィックスの場合に上げる)としてバージョンを定義して管理する方法です。

semantic-releaseとは?

semantic-releaseとは、コミットメッセージの内容から判断して、自動的に適切にバージョン(セマンティック・バージョニングに従う)を上げてNPMへ公開するためのモジュールです。これを導入すれば、コミットメッセージだけでNPMモジュールを管理できるようになります。コミットメッセージのルールはカスタマイズできますが、デフォルトではAngular Commit Message Conventionsに従う形になり、以下のようになります。

  • feat: 新しい機能の追加(Minor versionを上げる)
  • fix: バグフィックス(Patch versionを上げる)
  • docs: ドキュメントだけ変更
  • style: フォーマットの変更などのソースコードに影響を与えない変更
  • refactor: バグフィックスや機能追加を伴わないソースコードの変更
  • perf: パフォーマンス改善のためのソースコードの変更
  • test: 不足しているテストコードの追加や既存のテストコードの修正
  • chore: ビルドプロセスや補助的なツールやライブラリの変更

自作のNPMモジュールにsemantic-releaseを適用する

それでは、既存の自作のNPMモジュールのプロジェクトに適用してみましょう。ビルドツールはTravis CI、パッケージ管理はYarnを使うことにします。(代わりにビルドツールとしてCircle CI、パッケージ管理ツールとしてNPMを使うことももちろんできます。)

semantic-releaseをセットアップする

まずは、公式ドキュメント通りに、CLIを入れて、セットアップします。

$ yarn global add semantic-release-cli
$ cd my-npm-module
$ semantic-release-cli setup
? What is your npm registry? https://registry.npmjs.org/
? What is your npm username? you
? What is your npm password? [hidden]
? What is your GitHub username? you
? What is your GitHub password? [hidden]
? What CI are you using? Travis CI
Please refer to https://github.com/semantic-release/semantic-release/blob/master/docs/recipes/travis.md to configure your .travis.yml file.
$ cat package.json
{
...
  "version": "0.0.0-development",
...
  "repository": "https://github.com/you/my-npm-module.git",
...
  "devDependencies": {
...
    "semantic-release": "^15.13.3"
  },
...
  "scripts": {
...
    "semantic-release": "semantic-release"
  },
...
}

バージョンにはデフォルトで「0.0.0-development」と書かれますが、semantic-releaseではpackage.jsonのversionは使わず、NPM側のバージョンが使われます。

Travis CI用の設定ファイルを設定する

「.travis.yml」に以下のように設定します。テストを実行し、テストが成功してかつバージョンが上がっている場合にNPMに公開させる設定です。

language: node_js
node_js:
  - 10
  - 8

jobs:
  include:
    - stage: release
      node_js: lts/*
      deploy:
        provider: script
        skip_cleanup: true
        script:
          - yarn semantic-release

Travis CI用の設定方法は公式ドキュメントを参照してください。

commitizenを設定する

コミットメッセージのルールが決まっていても手動で入力するとミスがおきます。なので、正しくコミットメッセージを入力するために「commitizen」の「cz-cli」のモジュールを使います。ついでに、チェンジログを自動で追加してくれる「cz-conventional-changelog」のモジュールも導入します。

$ yarn add --dev commitizen
$ yarn commitizen init cz-conventional-changelog --yarn --dev --exact
$ cat package.json
{
...
  "devDependencies": {
...
    "cz-conventional-changelog": "2.1.0",
  },
...
  "config": {
    "commitizen": {
      "path": "./node_modules/cz-conventional-changelog"
    }
  }
}
$ vi package.json
{
...
  "scripts": {
    "commit": "git-cz",
...
  },
...
}

これで設定は完了です。グローバルに導入してもよいですが、全てのプロジェクトに適用することはあまりないと思うので、モジュール内に導入しています。

使い方

最後に、使ってみましょう。今回は何かバグフィックスをしたと仮定してコミットしてみます。

$ git add .
$ yarn commit
...
? Select the type of change that you're committing: (Use arrow keys)
  feat:     A new feature 
❯ fix:      A bug fix 
  docs:     Documentation only changes 
  style:    Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) 
  refactor: A code change that neither fixes a bug nor adds a feature 
  perf:     A code change that improves performance 
  test:     Adding missing tests or correcting existing tests 
(Move up and down to reveal more choices)
...
? Select the type of change that you're committing: fix:       A bug fix
? What is the scope of this change (e.g. component or file name)? (press enter to skip)
 
? Write a short, imperative tense description of the change:
 Fix any bugs
? Provide a longer description of the change: (press enter to skip)
 
? Are there any breaking changes? No
? Does this change affect any open issues? No

[master 043b0c2] fix: Fix any bugs
$ git push origin master

これでTravis CIでテストが成功すれば、Patch versionが上がり、NPMに公開されます。

最後に

いかがでしたか?これで自作のNPMモジュールをsemantic-releaseを使うことで、セマンティック・バージョニングでの管理が自動的にできるようになったと思います。それでは。

環境

  • yarn: 1.13.0
  • semantic-release: 15.13.3
  • commitizen: 3.0.5
  • cz-conventional-changelog: 2.1.0

カテゴリ : 技術 Tips & Tutorials タグ : npm, semantic-release, semantic-versioning

2019年2月4日 By Keid Leave a Comment

Go言語でさくっとREST APIを作ろう(Gorilla Mux編)

Go言語でさくっとREST APIを作ろう(Gorilla Mux編)

Go言語でGorilla Muxというルーティング用のライブラリを使って簡単なCRUDのREST APIを作る方法を紹介します。

はじめに

Go言語にはRubyのRailsやPythonのDjango、JavaScriptのExpressのようなAPIを作る上で絶対的なフレームワークはなく、複数のフレームワークが乱立しているような状態です。例えば、Revel、Gin、Martini、Web.go、Gojiなどがありますが、今回はGorilla web toolkitの中のGorilla Muxというルーティング用のライブラリを使ってAPIを実装することにします。

題材は何でも良いのですが、Twitterっぽいツイートを管理するAPIにしてみましょう。それでは、Go言語で簡単なREST APIを作っていきましょう!

Gorilla Muxとは?

Gorilla Muxは、Gorilla web toolkitというGo言語用のWebツールキット郡の一つで、主にルーティング用のライブラリです。

前提

以下の準備が完了している必要があります。

  • Goがインストールされていること
  • Dockerがインストールされていること

詳しいバージョンは「環境」を参照してください。

PostgreSQLの準備とテーブル作成

テーブル作成用のSQLファイルの準備

今回はデータベースとしてPostgreSQLを使い、Dockerで準備します。Dockerで起動時にテーブルを作成したいので、それ用のSQLファイルを準備します。

$ mkdir initdb
$ touch initdb/1_create_tables.sql

「1_create_tables.sql」は以下のようにします。

create table tweets
(
  id serial primary key,
  content text not null,
  user_name text not null,
  comment_num integer not null default 0,
  star_num integer not null default 0,
  re_tweet_num integer not null default 0
);

「tweets」テーブルを作成するSQLです。

データベースとテーブルの作成

DockerでPostgreSQLを準備し、起動時に先程作成したSQLを読み込ませます。

$ docker run --rm --name my_postgres -p 5432:5432 -e POSTGRES_USER=puser -e POSTGRES_PASSWORD=ppassword -e POSTGRES_DB=testdb -v $PWD/initdb:/docker-entrypoint-initdb.d -d postgres
$ psql -h localhost -p 5432 -U puser -d testdb
Password for user puser:
psql (11.1)
Type "help" for help.

testdb-# \dt
        List of relations
 Schema |  Name  | Type  | Owner
--------+--------+-------+-------
 public | tweets | table | puser
(1 row)

testdb-# \d tweets
                               Table "public.tweets"
    Column    |  Type   | Collation | Nullable |              Default
--------------+---------+-----------+----------+------------------------------------
 id           | integer |           | not null | nextval('tweets_id_seq'::regclass)
 content      | text    |           | not null |
 user_name    | text    |           | not null |
 comment_num  | integer |           | not null | 0
 star_num     | integer |           | not null | 0
 re_tweet_num | integer |           | not null | 0
Indexes:
    "tweets_pkey" PRIMARY KEY, btree (id)

testdb=# \q

うまくテーブル作成まで完了しました。

今回は一時的なデータベースなので、「––rm」オプションを指定しており、停止するとコンテナが削除されます。(データベース及びデータは残りません)また、公開ポートはデフォルトの5432としているので、ローカルでPostgreSQLが起動している場合は、ポート番号を変更してください。

APIの作成

まずは、必要なパッケージやデータベースを準備しましょう。

ベースプロジェクトの作成

プロジェクト用のフォルダを作成し、パッケージをインストールします。それから、今回実装に必要なフォルダとファイルを作成します。

$ mkdir golang-rest-api
$ cd golang-rest-api/
$ go mod init golang-rest-api
$ go get -u github.com/gorilla/mux
$ go get -u github.com/lib/pq
$ go mod graph
golang-rest-api github.com/gorilla/mux@v1.6.2
golang-rest-api github.com/lib/pq@v1.0.0
$ mkdir models
$ mkdir controllers
$ mkdir utils
$ touch main.go
$ touch models/tweet.go
$ touch models/error.go
$ touch controllers/controller.go
$ touch controllers/tweet.go
$ touch utils/respond.go
$ tree
.
├── controllers
│   ├── controller.go
│   └── tweet.go
├── go.mod
├── go.sum
├── main.go
├── models
│   ├── error.go
│   └── tweet.go
└── utils
    └── respond.go

全体像が見えるようになりました。

APIの実装

さくさく実装しましょう。

models/tweet.go

package models

type Tweet struct {
    ID         int    `json:"id"`
    Content    string `json:"content"`
    UserName   string `json:"user_name"`
    CommentNum int    `json:"comment_num"`
    StarNum    int    `json:"star_num"`
    ReTweetNum int    `json:"re_tweet_num"`
}

models/error.go

package models

type Error struct {
    Message string `json:"message"`
}

utils/respond.go

package utils

import (
    "encoding/json"
    "net/http"
)

func Respond(w http.ResponseWriter, status int, data interface{}) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(status)
    json.NewEncoder(w).Encode(data)
}

controllers/controller.go

package controllers

type Controller struct{}

controllers/tweet.go

package controllers

import (
    "database/sql"
    "encoding/json"
    "golang-rest-api/models"
    "golang-rest-api/utils"
    "log"
    "net/http"
    "strconv"

    "github.com/gorilla/mux"
)

func (c Controller) GetTweets(db *sql.DB) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        var tweet models.Tweet
        tweets := make([]models.Tweet, 0)
        var errorObj models.Error

        rows, err := db.Query("select * from tweets")
        if err != nil {
            log.Println(err)
            errorObj.Message = "Server error"
            utils.Respond(w, http.StatusInternalServerError, errorObj)
            return
        }

        defer rows.Close()
        for rows.Next() {
            err := rows.Scan(&tweet.ID, &tweet.Content, &tweet.UserName, &tweet.CommentNum, &tweet.StarNum,
                &tweet.ReTweetNum)
            if err != nil {
                log.Println(err)
                errorObj.Message = "Server error"
                utils.Respond(w, http.StatusInternalServerError, errorObj)
                return
            }

            tweets = append(tweets, tweet)
        }
        if err := rows.Err(); err != nil {
            log.Println(err)
            errorObj.Message = "Server error"
            utils.Respond(w, http.StatusInternalServerError, errorObj)
            return
        }

        utils.Respond(w, http.StatusOK, tweets)
    }
}

func (c Controller) GetTweet(db *sql.DB) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        var tweet models.Tweet
        var errorObj models.Error

        params := mux.Vars(r)
        id, err := strconv.Atoi(params["id"])
        if err != nil {
            errorObj.Message = "\"id\" is wrong"
            utils.Respond(w, http.StatusBadRequest, errorObj)
            return
        }

        rows := db.QueryRow("select * from tweets where id=$1", id)
        err = rows.Scan(&tweet.ID, &tweet.Content, &tweet.UserName, &tweet.CommentNum, &tweet.StarNum,
            &tweet.ReTweetNum)
        if err != nil {
            if err == sql.ErrNoRows {
                errorObj.Message = "The tweet does not exist"
                utils.Respond(w, http.StatusBadRequest, errorObj)
                return
            }
            log.Println(err)
            errorObj.Message = "Server error"
            utils.Respond(w, http.StatusInternalServerError, errorObj)
            return
        }

        utils.Respond(w, http.StatusOK, tweet)
    }
}

func (c Controller) AddTweet(db *sql.DB) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        var tweet models.Tweet
        var errorObj models.Error

        json.NewDecoder(r.Body).Decode(&tweet)
        if tweet.Content == "" {
            errorObj.Message = "\"content\" is missing"
            utils.Respond(w, http.StatusBadRequest, errorObj)
            return
        }
        if tweet.UserName == "" {
            errorObj.Message = "\"user_name\" is missing"
            utils.Respond(w, http.StatusBadRequest, errorObj)
            return
        }
        if tweet.CommentNum < 0 {
            errorObj.Message = "\"comment_num\" must be greater than or equal to 0"
            utils.Respond(w, http.StatusBadRequest, errorObj)
            return
        }
        if tweet.StarNum < 0 {
            errorObj.Message = "\"star_num\" must be greater than or equal to 0"
            utils.Respond(w, http.StatusBadRequest, errorObj)
            return
        }
        if tweet.ReTweetNum < 0 {
            errorObj.Message = "\"re_tweet_num\" must be greater than or equal to 0"
            utils.Respond(w, http.StatusBadRequest, errorObj)
            return
        }

        err := db.QueryRow(
            "insert into tweets (content, user_name, comment_num, star_num, re_tweet_num)"+
                " values($1, $2, $3, $4, $5) RETURNING id;",
            tweet.Content, tweet.UserName, tweet.CommentNum, tweet.StarNum, tweet.ReTweetNum).Scan(&tweet.ID)
        if err != nil {
            log.Println(err)
            errorObj.Message = "Server error"
            utils.Respond(w, http.StatusInternalServerError, errorObj)
            return
        }

        utils.Respond(w, http.StatusCreated, tweet)
    }
}

func (c Controller) PutTweet(db *sql.DB) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        var tweet models.Tweet
        var errorObj models.Error

        params := mux.Vars(r)
        id, err := strconv.Atoi(params["id"])
        if err != nil {
            errorObj.Message = "\"id\" is wrong"
            utils.Respond(w, http.StatusBadRequest, errorObj)
            return
        }

        json.NewDecoder(r.Body).Decode(&tweet)
        if tweet.CommentNum < 0 {
            errorObj.Message = "\"comment_num\" must be greater than or equal to 0"
            utils.Respond(w, http.StatusBadRequest, errorObj)
            return
        }
        if tweet.StarNum < 0 {
            errorObj.Message = "\"star_num\" must be greater than or equal to 0"
            utils.Respond(w, http.StatusBadRequest, errorObj)
            return
        }
        if tweet.ReTweetNum < 0 {
            errorObj.Message = "\"re_tweet_num\" must be greater than or equal to 0"
            utils.Respond(w, http.StatusBadRequest, errorObj)
            return
        }

        var update models.Tweet
        rows := db.QueryRow("select * from tweets where id=$1", id)
        err = rows.Scan(&update.ID, &update.Content, &update.UserName, &update.CommentNum,
            &update.StarNum, &update.ReTweetNum)
        if err != nil {
            if err == sql.ErrNoRows {
                errorObj.Message = "The tweet does not exist"
                utils.Respond(w, http.StatusBadRequest, errorObj)
                return
            }
            log.Println(err)
            errorObj.Message = "Server error"
            utils.Respond(w, http.StatusInternalServerError, errorObj)
            return
        }

        if tweet.Content != "" {
            update.Content = tweet.Content
        }
        if tweet.UserName != "" {
            update.UserName = tweet.UserName
        }
        if tweet.CommentNum > 0 {
            update.CommentNum = tweet.CommentNum
        }
        if tweet.StarNum > 0 {
            update.StarNum = tweet.StarNum
        }
        if tweet.ReTweetNum > 0 {
            update.ReTweetNum = tweet.ReTweetNum
        }

        result, err := db.Exec(
            "update tweets set content=$1, user_name=$2, comment_num=$3, star_num=$4, re_tweet_num=$5"+
                " where id=$6 RETURNING id",
            &update.Content, &update.UserName, &update.CommentNum, &update.StarNum, &update.ReTweetNum, id)
        if err != nil {
            log.Println(err)
            errorObj.Message = "Server error"
            utils.Respond(w, http.StatusInternalServerError, errorObj)
            return
        }

        _, err = result.RowsAffected()
        if err != nil {
            if err == sql.ErrNoRows {
                errorObj.Message = "The tweet does not exist"
                utils.Respond(w, http.StatusBadRequest, errorObj)
                return
            }
            log.Println(err)
            errorObj.Message = "Server error"
            utils.Respond(w, http.StatusInternalServerError, errorObj)
            return
        }

        utils.Respond(w, http.StatusOK, update)
    }
}

func (c Controller) RemoveTweet(db *sql.DB) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        var errorObj models.Error

        params := mux.Vars(r)
        id, err := strconv.Atoi(params["id"])
        if err != nil {
            errorObj.Message = "\"id\" is wrong"
            utils.Respond(w, http.StatusBadRequest, errorObj)
            return
        }

        result, err := db.Exec("delete from tweets where id = $1", id)
        if err != nil {
            log.Println(err)
            errorObj.Message = "Server error"
            utils.Respond(w, http.StatusInternalServerError, errorObj)
            return
        }

        _, err = result.RowsAffected()
        if err != nil {
            if err == sql.ErrNoRows {
                errorObj.Message = "The tweet does not exist"
                utils.Respond(w, http.StatusBadRequest, errorObj)
                return
            }
            log.Println(err)
            errorObj.Message = "Server error"
            utils.Respond(w, http.StatusInternalServerError, errorObj)
            return
        }

        utils.Respond(w, http.StatusNoContent, "")
    }
}

main.go

package main

import (
    "database/sql"
    "golang-rest-api/controllers"
    "log"
    "net/http"

    "github.com/gorilla/mux"
    "github.com/lib/pq"
)

func main() {
    pgURL, err := pq.ParseURL("postgres://puser:ppassword@localhost:5432/testdb?sslmode=disable")
    if err != nil {
        log.Fatal(err)
    }

    db, err := sql.Open("postgres", pgURL)
    if err != nil {
        log.Fatal(err)
    }

    controller := controllers.Controller{}

    router := mux.NewRouter()

    router.HandleFunc("/api/tweets", controller.GetTweets(db)).Methods("GET")
    router.HandleFunc("/api/tweets/{id}", controller.GetTweet(db)).Methods("GET")
    router.HandleFunc("/api/tweets", controller.AddTweet(db)).Methods("POST")
    router.HandleFunc("/api/tweets/{id}", controller.PutTweet(db)).Methods("PUT")
    router.HandleFunc("/api/tweets/{id}", controller.RemoveTweet(db)).Methods("DELETE")

    log.Println("Server up on port 3000...")
    log.Fatal(http.ListenAndServe(":3000", router))
}

これで実装完了です。ブログ記事にする関係でモジュール化を控えめにしているので、少々長いコードになっていますがご了承ください(笑)

動作確認

最後に作ったAPIの動作確認をしてみましょう。

APIを起動します。

$ go run main.go
2019/01/20 21:47:03 Server up on port 3000...

curlでCRUDを確認します。

$ curl -v -H "Accept:application/json" -H "Content-Type:application/json" -X POST -d '{"content":"This is my first tweet.","user_name":"@keidrun","comment_num":5,"star_num":15,"re_tweet_num":25}' http://localhost:3000/api/tweets | jq
...
< HTTP/1.1 201 Created
...
{
  "id": 1,
  "content": "This is my first tweet.",
  "user_name": "@keidrun",
  "comment_num": 5,
  "star_num": 15,
  "re_tweet_num": 25
}
$ curl -v -H "Accept:application/json" -H "Content-Type:application/json" -X POST -d '{"content":"Golang is my favorite language!","user_name":"@superdeveloper","comment_num":22,"star_num":222,"re_tweet_num":2222}' http://localhost:3000/api/tweets | jq
...
< HTTP/1.1 201 Created
...
{
  "id": 2,
  "content": "Golang is my favorite language!",
  "user_name": "@superdeveloper",
  "comment_num": 22,
  "star_num": 222,
  "re_tweet_num": 2222
}
$ curl -v -H "Accept:application/json" -H "Content-Type:application/json" -X POST -d '{"content":"I am nothing. Just an ordinary guy.","user_name":"@person"}' http://localhost:3000/api/tweets | jq
...
< HTTP/1.1 201 Created
...
{
  "id": 3,
  "content": "I am nothing. Just an ordinary guy.",
  "user_name": "@person",
  "comment_num": 0,
  "star_num": 0,
  "re_tweet_num": 0
}
$ curl -v -H "Accept:application/json" http://localhost:3000/api/tweets | jq
...
< HTTP/1.1 200 OK
...
[
  {
    "id": 1,
    "content": "This is my first tweet.",
    "user_name": "@keidrun",
    "comment_num": 5,
    "star_num": 15,
    "re_tweet_num": 25
  },
  {
    "id": 2,
    "content": "Golang is my favorite language!",
    "user_name": "@superdeveloper",
    "comment_num": 22,
    "star_num": 222,
    "re_tweet_num": 2222
  },
  {
    "id": 3,
    "content": "I am nothing. Just an ordinary guy.",
    "user_name": "@person",
    "comment_num": 0,
    "star_num": 0,
    "re_tweet_num": 0
  }
]
$ curl -v -H "Accept:application/json" http://localhost:3000/api/tweets/1 | jq
...
< HTTP/1.1 200 OK
...
{
  "id": 1,
  "content": "This is my first tweet.",
  "user_name": "@keidrun",
  "comment_num": 5,
  "star_num": 15,
  "re_tweet_num": 25
}
$ curl -v -H "Accept:application/json" http://localhost:3000/api/tweets/2 | jq
...
< HTTP/1.1 200 OK
...
{
  "id": 2,
  "content": "Golang is my favorite language!",
  "user_name": "@superdeveloper",
  "comment_num": 22,
  "star_num": 222,
  "re_tweet_num": 2222
}
$ curl -v -H "Accept:application/json" http://localhost:3000/api/tweets/3 | jq
...
< HTTP/1.1 200 OK
...
{
  "id": 3,
  "content": "I am nothing. Just an ordinary guy.",
  "user_name": "@person",
  "comment_num": 0,
  "star_num": 0,
  "re_tweet_num": 0
}
$ curl -v -H "Accept:application/json" -H "Content-Type:application/json" -X PUT -d '{"content":"I am excellent guy!!","user_name":"@awesomeperson","comment_num":99,"star_num":999,"re_tweet_num":9999}' http://localhost:3000/api/tweets/3 | jq
...
< HTTP/1.1 200 OK
...
{
  "id": 3,
  "content": "I am excellent guy!!",
  "user_name": "@awesomeperson",
  "comment_num": 99,
  "star_num": 999,
  "re_tweet_num": 9999
}
$ curl -v -H "Accept:application/json" -X DELETE http://localhost:3000/api/tweets/2 | jq
...
< HTTP/1.1 204 No Content
...
$ curl -v -H "Accept:application/json" http://localhost:3000/api/tweets | jq
...
< HTTP/1.1 200 OK
...
[
  {
    "id": 1,
    "content": "This is my first tweet.",
    "user_name": "@keidrun",
    "comment_num": 5,
    "star_num": 15,
    "re_tweet_num": 25
  },
  {
    "id": 3,
    "content": "I am excellent guy!!",
    "user_name": "@awesomeperson",
    "comment_num": 99,
    "star_num": 999,
    "re_tweet_num": 9999
  }
]

大丈夫そうですね。これで動作確認は完了です。

最後に

いかがでしたか?これでGo言語でGorilla Muxを使ってAPIを作成できるようになったと思います。では。

環境

  • Go: 1.11.4
  • mux: 1.6.2
  • pq: 1.6.2
  • Docker: 18.09.1, build 4c52b90

カテゴリ : 技術 Tips & Tutorials タグ : golang, gorilla-mux, rest-api

2019年2月1日 By Keid Leave a Comment

MacBook ProでApp Storeのアプリをアップデートする際に「This item is temporarily unavailable」と怒られた場合の対処法

MacBook ProでApp Storeのアプリをアップデートする際に「This item is temporarily unavailable」と怒られた場合の対処法

MacBook Proのアプリのアップデートに失敗した場合の対処法を簡単なTipsとして紹介します。

はじめに

一ヶ月前に新しいMacBook Proを購入してしばらく使っていたのですが、ある日App Storeでアプリをアップデートしようとして「This item is temporarily unavailable」というエラーで失敗してしまいました。エラー内容を見て、数日して再度実行してみますが、同じエラーがでるだけでした。

今回は同じ症状が発生した人向けに、対処法を紹介します。

症状

App Storeのアップデートからアプリを更新しようとすると、以下のように「This item is temporarily unavailable」というエラーが表示され、一向に更新できない状態になっていると思います。

NewImage

ここで、コマンドラインから更新できる可能性を考慮して、masコマンドでアップデートしてみましょう。

$ mas outdated
539883307 LINE (5.11.2 -> 5.12.0)
1198319098 MarsEdit 4 (4.2.2 -> 4.2.4)
$ mas upgrade
Upgrading 2 outdated applications:
LINE (5.12.0), MarsEdit 4 (4.2.4)
No downloads
No downloads
Error: Download failed

すると以下のように新しいエラー「Account Not In This Store」が表示されます。

NewImage

この新しいエラーが表示された場合は「対処法の方法①」で解決できます。それ以外の場合は「対処法の方法②」を検討してください。

対処法

方法①アカウントとApp Storeのリージョンを同じにする

App Storeを起動し、「ストア -> マイアカウントを表示」をクリックします。

NewImage

「View Information」をクリックします。

NewImage

「Sign In to App Store」画面でApple IDとパスワードを入力して「Sign In」をクリックします。

NewImage

サインインに成功するとApp Storeのリージョンが自動的にアカウントと同じになり、アプリのアップデートができるようになります。

NewImage

これで解決できました。

方法②MacOSをリカバリする

MacOSに不具合がある場合の最終手段はOSを復元する方法です。MacBookの場合はWifi環境さえあればOSの再インストールができます。ただし、その場合はハードディスクのバックアップを取るなどめんどうな作業が発生するので、慎重に判断しましょう。やり方は公式のサポートページに記載されています。

最後に

いかがでしたか?問題は解決できたでしょうか?私の場合はカナダで購入したMacBookだったため、日本のアカウントとApp Storeのリージョンにミスマッチが発生していたようです。海外でApple製品を購入する場合はアカウントとApp Storeのリージョンを意識しておくと良いでしょう。では。

環境

  • PC: MacBook Pro 15-inch, 2018
  • OS: macOS Mojave 10.14.2
  • App Store: 3.0 (1003.2)
  • mas-cli: 1.5

カテゴリ : ライフハック タグ : app-store, mac

  • 1
  • 2
  • 3
  • …
  • 46
  • Next Page »

技術力と英語力を向上させて
あたなを次のステージへ引き上げるための無料メルマガ

Sponsored Links

About Author

Keid

カナダを拠点に活躍する(予定)デベロッパー。 大学でコンピュータサイエンスを真面目に学び、日本の大手IT企業に就職したまでは予定通りの人生だったが、日本のIT業界に失望したことで考え方が変わり、海外への挑戦を決意。海外に行きたくても英語ができなかっため、語学留学を経て強制的に英語を上達させ、カナダへの切符を手にした。このブログでは海外に挑戦したいエンジニアやデザイナーに少しでも有益な情報を提供していきたいと思う。(写真は旅行で行ったラスベガス)

https://casualdevelopers.com/

最近の投稿

  • NPMモジュールを自動的にセマンティック・バージョニングで管理する方法(semantic-release編)

    NPMモジュールを自動的にセマンティック・バージョニングで管理する方法(semantic-release編)

    2019年2月15日
  • Go言語でさくっとREST APIを作ろう(Gorilla Mux編)

    Go言語でさくっとREST APIを作ろう(Gorilla Mux編)

    2019年2月4日
  • MacBook ProでApp Storeのアプリをアップデートする際に「This item is temporarily unavailable」と怒られた場合の対処法

    MacBook ProでApp Storeのアプリをアップデートする際に「This item is temporarily unavailable」と怒られた場合の対処法

    2019年2月1日
  • Go言語のためのVisual Studio Codeの設定方法

    Go言語のためのVisual Studio Codeの設定方法

    2019年1月28日
  • reCAPTCHAをWordPressに導入してロボットによるスパムメールを防ぐ方法

    reCAPTCHAをWordPressに導入してロボットによるスパムメールを防ぐ方法

    2019年1月25日

カテゴリ

  • 技術 Tips & Tutorials (85)
  • 技術塾 (5)
  • ライフハック (25)
  • 海外留学 (11)
  • 英語学習 (3)
  • コラム (7)

アーカイブ

最高の学習のために

人気記事ランキング

  • Jupyter Notebookで「The kernel appears to have died. It will restart automatically.」というエラーが出た場合の原因と対処法
    Jupyter Notebookで「The kernel appears to have died. It will restart automatically.」というエラーが出た場合の原因と対処法
  • MySQLで「ERROR 2003 (HY000): Can't connect to MySQL server」と怒られた時の対処法
    MySQLで「ERROR 2003 (HY000): Can't connect to MySQL server」と怒られた時の対処法
  • データサイエンスのためのAnaconda環境構築とTensorflowのインストール方法(Docker編)
    データサイエンスのためのAnaconda環境構築とTensorflowのインストール方法(Docker編)
  • Herokuの無料dynoをスリープさせないで24時間稼働させる4つの方法
    Herokuの無料dynoをスリープさせないで24時間稼働させる4つの方法
  • [tips][Sublime Text] Sublime Text 3で文字化けしない方法
    [tips][Sublime Text] Sublime Text 3で文字化けしない方法
  • PythonでWebスクレイピング入門(Scrapy+Selenium編)
    PythonでWebスクレイピング入門(Scrapy+Selenium編)
  • SAKURAのメールボックスで独自ドメインのメールを設定し、Gmail経由で送受信する方法
    SAKURAのメールボックスで独自ドメインのメールを設定し、Gmail経由で送受信する方法
  • TumblrからWordPressにブログ移転する最適な方法
    TumblrからWordPressにブログ移転する最適な方法
  • バンクーバー留学豆知識:バンクーバーのATMで日本の銀行のキャッシュカードを使ってお得にお金を引き出す方法
    バンクーバー留学豆知識:バンクーバーのATMで日本の銀行のキャッシュカードを使ってお得にお金を引き出す方法
  • [tips][Windows] Windows Updateを一括で行う方法
    [tips][Windows] Windows Updateを一括で行う方法

Bitcoin寄付

Bitcoinを寄付しよう

BTC
Select Payment Method
Personal Info

Donation Total: BTC 0.0010

このブログの運営のためにBitcoinでの寄付を募集しています。お気持ち程度の寄付を頂けると管理者の励みになります。

Bitcoin寄付について知りたい方はこちらの記事へ

ビットコイン取引ならここ

  • ホーム
  • 技術 Tips & Tutorials
  • 技術塾
  • ライフハック
  • 海外留学
  • 英語学習
  • コラム
  • サイトマップ
  • タグ一覧
  • プライバシーポリシー
  • お問い合わせ

Copyright © 2019 Keid - Casual Developers Notes