Casual Developers Note

エンジニアやデザイナー向けの技術情報・英語学習情報・海外留学情報・海外旅行情報を提供中。世界を旅して人生を楽しもう。

  • ホーム
  • 技術 Tips & Tutorials
  • 技術塾
  • ライフハック
  • 海外留学
  • 英語学習
  • コラム
  • お問い合わせ
現在の場所:ホーム / アーカイブrest-api

2019年6月28日 By KD コメントを書く

TypeScriptのためのバックエンド用フレームワークNestJSにさくさく入門しよう

TypeScriptのためのバックエンド用フレームワークNestJSにさくさく入門しよう

TypeScriptのためにAngularからインスパイアされて作られたバックエンド用のフレームワークNestJSについて紹介します。

はじめに

最近勢いがあるTypeScriptですが、皆さんはバックエンドのAPIを構築する場合はどんなフレームワークを使っていますか?TypeScriptを使わない時と同じようにExpressやHapiなどを使っている人が多いと思いますが、もう一つ新しい選択肢があります。それはNestJSです。特徴は、Angularのようにバックエンドを書ける点と、結果としてフォルダやファイルの構成が明確化されているので、構成に悩むことなく本来のAPI開発に専念できる点です。

今回はNestJSで簡単なCRUDのAPIをさくさく作ってみましょう。

NestJSとは?

NestJSとは、TypeScriptおよびモダンJavaScriptのために作られたエンタープライズ級のNodeJSフレームワークです。冒頭でも述べたとおり、Angularにインスパイアされているため、Angularのような書き方でコーディングすることができます。(デコレータを使ったコーディングですね。)フォルダやファイルの構成も分かりやすく、フレームワークとして必要な機能もしっかりと有しています。ドキュメントもしっかり書かれており、サンプルとなるソースコードも公開されているので、すぐに開発が始められます。

簡単なAPIを作ってみよう

それでは、今回はユーザを管理する簡単なAPIを作ってみましょう。

ベースを作る

NestCLIコマンドを使ってプロジェクトを生成し、必要なフォルダとファイルを作成します。

$ yarn add glbal @nestjs/cli
$ nest --version
6.5.0
$ nest new my-nestjs-api
...
? Which package manager would you ❤️  to use? yarn
...
$ cd my-nestjs-api/
$ yarn add uuid @types/uuid joi @types/joi
$ mkdir -p src/common/pipes
$ mkdir -p src/users
$ mkdir -p src/users/models
$ mkdir -p src/users/dto
$ mkdir -p src/users/schemas
$ touch src/common/pipes/validation.pipe.ts
$ touch src/users/users.service.ts
$ touch src/users/users.controller.ts
$ touch src/users/users.module.ts
$ touch src/users/models/user.model.ts
$ touch src/users/schemas/create-user.schema.ts
$ touch src/users/schemas/update-user.schema.ts
$ touch src/users/schemas/user.enum.ts
$ touch src/users/users.controller.spec.ts
$ rm src/app.service.ts 
$ rm src/app.controller.ts 
$ rm src/app.controller.spec.ts
$ tree src/
src/
├── app.module.ts
├── common
│   └── pipes
│       └── validation.pipe.ts
├── main.ts
└── users
    ├── dto
    │   ├── create-user.dto.ts
    │   └── update-user.dto.ts
    ├── models
    │   └── user.model.ts
    ├── schemas
    │   ├── create-user.schema.ts
    │   ├── update-user.schema.ts
    │   └── user.enum.ts
    ├── users.controller.spec.ts
    ├── users.controller.ts
    ├── users.module.ts
    └── users.service.ts

構成ができました。

ユーザをCRUDするAPIを作る

ユーザを管理するCRUDのAPIを実装しましょう。

create-user.dto.ts

export class CreateUserDto {
  readonly name: string;
  readonly gender: string;
  readonly age: number;
}

update-user.dto.ts

export class UpdateUserDto {
  readonly name: string;
  readonly gender: string;
  readonly age: number;
}

user.model.ts

export class User {
  constructor(
    public id: string,
    public name: string,
    public gender: string,
    public age: number,
  ) {}
}

users.service.ts

import { Injectable, NotFoundException } from '@nestjs/common';
import { v4 as uuid } from 'uuid';
import { User } from './models/user.model';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';

@Injectable()
export class UserService {
  private users: User[] = [];

  createUser(createUserDto: CreateUserDto) {
    const userId = uuid();
    const newUser = new User(
      userId,
      createUserDto.name,
      createUserDto.gender,
      createUserDto.age,
    );
    this.users.push(newUser);
    return userId;
  }

  readAllUsers() {
    return [...this.users];
  }

  readUser(userId: string) {
    const [user, _] = this.findUser(userId);
    return { ...user };
  }

  updateUser(userId: string, updateUserDto: UpdateUserDto) {
    const [user, index] = this.findUser(userId);
    const updateUser = { ...user };
    if (updateUserDto.name) {
      updateUser.name = updateUserDto.name;
    }
    if (updateUserDto.gender) {
      updateUser.gender = updateUserDto.gender;
    }
    if (updateUserDto.age) {
      updateUser.age = updateUserDto.age;
    }
    this.users[index] = { ...updateUser };
  }

  deleteUser(userId: string) {
    const [user, _] = this.findUser(userId);
    this.users = this.users.filter(u => u.id !== user.id);
  }

  private findUser(id: string): [User, number] {
    const index = this.users.findIndex(u => u.id === id);
    const user = this.users[index];
    if (!user) {
      throw new NotFoundException(`Could not find the user of id: ${id}`);
    }
    return [user, index];
  }
}

users.controller.ts

import {
  Body,
  Controller,
  Get,
  Post,
  UsePipes,
  Param,
  Patch,
  Delete,
} from '@nestjs/common';
import { UserService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';

@Controller('users')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Post()
  addUser(@Body() createUserDto: CreateUserDto) {
    const userId = this.userService.createUser(createUserDto);
    return {
      id: userId,
    };
  }

  @Get()
  getAllUsers() {
    return this.userService.readAllUsers();
  }

  @Get(':id')
  getUser(@Param('id') id: string) {
    return this.userService.readUser(id);
  }

  @Patch(':id')
  updateUser(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
    this.userService.updateUser(id, updateUserDto);
    return null;
  }

  @Delete(':id')
  removeUser(@Param('id') id: string) {
    this.userService.deleteUser(id);
    return null;
  }
}

users.module.ts

import { Module } from '@nestjs/common';
import { UserController } from './users.controller';
import { UserService } from './users.service';

@Module({
  controllers: [UserController],
  providers: [UserService],
})
export class UserModule {}

app.module.ts

import { Module } from '@nestjs/common';
import { UserModule } from './users/users.module';

@Module({
  imports: [UserModule],
  controllers: [],
  providers: [],
})
export class AppModule {}

main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

一通り完成しました。

バリデーションを追加する

PipeにJoiでのバリデーションを追加しましょう。

user.enum.ts

export enum Gender {
  Male = 'male',
  Female = 'female',
}

create-user.schema.ts

import * as Joi from 'joi';
import { Gender } from './user.enum';

export const createUserSchema = Joi.object().keys({
  name: Joi.string()
    .min(3)
    .required(),
  gender: Joi.string()
    .valid([Gender.Male, Gender.Female])
    .required(),
  age: Joi.number()
    .integer()
    .min(0)
    .required(),
});

update-user.schema.ts

import * as Joi from 'joi';
import { Gender } from './user.enum';

export const updateUserSchema = Joi.object().keys({
  name: Joi.string().min(3),
  gender: Joi.string().valid([Gender.Male, Gender.Female]),
  age: Joi.number()
    .integer()
    .min(0),
});

validation.pipe.ts

import * as Joi from 'joi';
import {
  PipeTransform,
  Injectable,
  ArgumentMetadata,
  BadRequestException,
} from '@nestjs/common';

@Injectable()
export class ValidationPipe implements PipeTransform {
  constructor(private readonly schema: Object) {}

  transform(value: any, metadata: ArgumentMetadata) {
    const { error } = Joi.validate(value, this.schema);
    if (error) {
      throw new BadRequestException(error);
    }
    return value;
  }
}

users.controller.ts (追記)

import {
  Body,
  Controller,
  Get,
  Post,
  UsePipes,
  Param,
  Patch,
  Delete,
} from '@nestjs/common';
import { UserService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { ValidationPipe } from '../common/pipes/validation.pipe';
import { createUserSchema } from './schemas/create-user.schema';
import { updateUserSchema } from './schemas/update-user.schema';

@Controller('users')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Post()
  @UsePipes(new ValidationPipe(createUserSchema))
  addUser(@Body() createUserDto: CreateUserDto) {
    const userId = this.userService.createUser(createUserDto);
    return {
      id: userId,
    };
  }

  @Get()
  getAllUsers() {
    return this.userService.readAllUsers();
  }

  @Get(':id')
  getUser(@Param('id') id: string) {
    return this.userService.readUser(id);
  }

  @Patch(':id')
  @UsePipes(new ValidationPipe(updateUserSchema))
  updateUser(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
    this.userService.updateUser(id, updateUserDto);
    return null;
  }

  @Delete(':id')
  removeUser(@Param('id') id: string) {
    this.userService.deleteUser(id);
    return null;
  }
}

これでバリデーションの追加ができました。

少しテストを書いてみる

最後に、コントローラーのテストを少し書いて動作確認しましょう。

users.controller.spec.ts

import { Test, TestingModule } from '@nestjs/testing';
import { UserController } from './users.controller';
import { CreateUserDto } from './dto/create-user.dto';
import { Gender } from './schemas/user.enum';
import { UserService } from './users.service';
import { UpdateUserDto } from './dto/update-user.dto';
import { NotFoundException } from '@nestjs/common';
describe('UserController', () => {
let userController: UserController;
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
controllers: [UserController],
providers: [UserService],
}).compile();
userController = app.get<UserController>(UserController);
});
describe('addUser', () => {
it('should return id of the new user', () => {
const newUser: CreateUserDto = {
name: 'Tom',
gender: Gender.Male,
age: 22,
};
expect(userController.addUser(newUser)).toHaveProperty('id');
});
});
describe('getAllUsers', () => {
it('should return all users', () => {
const users: CreateUserDto[] = [
{
name: 'Tom',
gender: Gender.Male,
age: 22,
},
{
name: 'Mary',
gender: Gender.Female,
age: 19,
},
{
name: 'Keid',
gender: Gender.Male,
age: 30,
},
];
userController.addUser(users[0]);
userController.addUser(users[1]);
userController.addUser(users[2]);
expect(userController.getAllUsers()).toEqual(
users.map(user => ({ ...user, id: expect.anything() })),
);
});
});
describe('getUser', () => {
it('should return a user', () => {
const users: CreateUserDto[] = [
{
name: 'Tom',
gender: Gender.Male,
age: 22,
},
{
name: 'Mary',
gender: Gender.Female,
age: 19,
},
{
name: 'Keid',
gender: Gender.Male,
age: 30,
},
];
const userId0 = userController.addUser(users[0]).id;
const userId1 = userController.addUser(users[1]).id;
const userId2 = userController.addUser(users[2]).id;
expect(userController.getUser(userId0)).toEqual({
...users[0],
id: userId0,
});
expect(userController.getUser(userId1)).toEqual({
...users[1],
id: userId1,
});
expect(userController.getUser(userId2)).toEqual({
...users[2],
id: userId2,
});
});
});
describe('updateUser', () => {
it('should update the user', () => {
const newUser: CreateUserDto = {
name: 'Mary',
gender: Gender.Female,
age: 19,
};
const addedUserId = userController.addUser(newUser).id;
const updateUser: UpdateUserDto = {
name: 'Super Man',
gender: Gender.Male,
age: 100,
};
userController.updateUser(addedUserId, updateUser);
expect(userController.getUser(addedUserId)).toEqual({
...updateUser,
id: addedUserId,
});
});
});
describe('removeUser', () => {
it('should remove the user', () => {
const users: CreateUserDto[] = [
{
name: 'Keid',
gender: Gender.Male,
age: 30,
},
{
name: 'Jobs',
gender: Gender.Male,
age: 56,
},
];
const userId0 = userController.addUser(users[0]).id;
const userId1 = userController.addUser(users[1]).id;
userController.removeUser(userId1);
expect(userController.getAllUsers()).toHaveLength(1);
expect(() => userController.getUser(userId1)).toThrow();
});
});
});

テストを実行してみましょう。

$ yarn test
...
PASS  src/users/users.controller.spec.ts
UserController
addUser
✓ should return id of the new user (11ms)
getAllUsers
✓ should return all users (4ms)
getUser
✓ should return a user (2ms)
updateUser
✓ should update the user (2ms)
removeUser
✓ should remove the user (2ms)
Test Suites: 1 passed, 1 total
Tests:       5 passed, 5 total
Snapshots:   0 total
Time:        2.743s, estimated 3s
Ran all test suites.
✨  Done in 3.83s.

OKですね。

最後に

いかがでしたか?これでNestJSの基本はマスターできたと思います。ルールが決まっているので簡単に実装が始められるところが良いですね。ちなみに、現在NestJSのGitHubページ上に日本語のドキュメントが無いので、コントリビュートするチャンスですよ(笑)それでは。

環境

  • NodeJS: v12.3.1
  • Yarn: 1.16.0
  • TypeScript: 3.5.2
  • NestJS: 6.5.0

カテゴリ : 技術 Tips & Tutorials タグ : nestjs, nodejs, rest-api, typescript

2019年3月11日 By KD コメントを書く

GatsbyJSをWordPressのHeadless CMSのREST APIと連携する方法(JWT認証編)

GatsbyJSをWordPressのHeadless CMSのREST APIと連携する方法(JWT認証編)

React製の静的サイトジェネレーターであるGatsbyJSと、Headless CMSとして機能するWordPressのREST APIを連携させて、バックエンドはWordPressで管理しつつ、フロントエンドをGatsbyJSで簡単に構築する方法を紹介します。

はじめに

最近注目を集めているReact製の静的サイトジェネレーターであるGatsbyJSと、デフォルトでHeadless CMSとして機能させることができるWordPressのREST APIを連携させることで、WordPressの管理画面の使い勝手を維持したまま、フロントエンドをモダンに書き換えることが可能です。CMSを構築する上で今最も暑い組み合わせです。さらに、今回はJWT認証の設定までさらっと完成させます。

それでは、GatsbyJSとWordPress REST APIを連携させて動かしてみましょう。

GatsbyJSとは?

GatsbyJSとは、ReactとGraphQLを前提とした静的サイトジェネレーターです。静的サイトなので、ページの表示の際にGraphQLで必要な情報を1回取得し、静的ページを生成して表示する仕組みです。開発はReactで行うことになります。

前提

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

  • Dockerがインストールされていること
  • NodeJSがインストールされていること
  • WordPressのREST APIでJWT認証設定が完了している開発環境が構築済みであること(この環境がない人は以前の記事「WordPressのHeadless CMSのREST APIを使う開発環境をDocker上に構築する方法(JWT認証編)」を参照してください。)

詳しくは「環境」を参照してください。

WordPress REST APIとテストデータの準備

それでは、GatsbyJSとWordPress REST APIを連携していきましょう。

WordPressのREST APIを起動する

まずは、WordPress REST APIを起動します。WordPress REST APIの開発環境は以前の記事「WordPressのHeadless CMSのREST APIを使う開発環境をDocker上に構築する方法(JWT認証編)」から持ってきて使います。

$ cd wordpress-headless-cms
$ docker-compose up --build -d
$ docker-compose ps
Name              Command               State                 Ports              
---------------------------------------------------------------------------------
db     docker-entrypoint.sh --def ...   Up      0.0.0.0:3306->3306/tcp, 33060/tcp
web    docker-php-entrypoint apac ...   Up      80/tcp, 0.0.0.0:8080->8080/tcp

以前の記事のWordPressの設定がすべて完了している前提で進めますが、一応補足しておくと、以下の2つのプラグインのインストールと設定が必要です。

  • JWT Authentication for WP REST API
  • WP API Menus

WordPressにテストデータを入れる

今回用の固定ページおよびヘッダーメニューのテストデータを準備します。

$ docker exec web wp post delete $(docker exec web wp post list --post_type=page,post --format=ids)
$ docker exec web wp post create --post_type=page --post_title="Welcome Page" --post_content="Welcome to GatsbyJS page with WordPress;)" --post_name=welcom --post_status=publish
$ docker exec web wp post create --post_type=page --post_title="Test Page" --post_content="Let's getting started with GatsbyJS and WordPress Headless CMS\!" --post_name=test --post_status=publish
$ curl http://localhost:8080/wp-json/wp/v2/pages | jq
[
{
"id": 9,
...
"slug": "test",
"status": "publish",
"type": "page",
"link": "http://localhost:8080/test/",
"title": {
"rendered": "Test Page"
},
"content": {
"rendered": "

Let’s getting started with GatsbyJS and WordPress Headless CMS\\!

\n", "protected": false }, ... } }, { "id": 8, ... "slug": "welcome", "status": "publish", "type": "page", "link": "http://localhost:8080/welcome/", "title": { "rendered": "Welcome Page" }, "content": { "rendered": "

Welcome to GatsbyJS page with WordPress;)

\n", "protected": false }, ... } } ] $ docker exec web wp menu delete $(docker exec web wp menu list --format=ids) $ docker exec web wp menu create "Header menu" $ curl http://localhost:8080/wp-json/wp-api-menus/v2/menus | jq [ { "term_id": 2, "name": "Header menu", "slug": "header-menu", ... "meta": { "links": { "collection": "http://localhost:8080/wp-json/wp-api-menus/v2/menus/", "self": "http://localhost:8080/wp-json/wp-api-menus/v2/menus/2" } } } ] $ docker exec web wp menu item add-post header-menu 8 --title="Welcome" $ docker exec web wp menu item add-post header-menu 9 --title="Test Page"

WordPressの管理画面にログインして確認してみましょう。

NewImage

NewImage

大丈夫ですね。

GatsbyJSとWordPress REST APIの連携

GatsbyJSプロジェクトを作成する

GatsbyJSのプロジェクトを生成し、必要なプラグインをインストールします。

$ npm install -g gatsby-cli
$ gatsby new frontend
$ cd frontend/
$ npm install --save gatsby-source-wordpress slash dotenv
$ rm yarn.lock
$ rm src/pages/index.js
$ rm src/pages/page-2.js
$ mkdir -p src/templates
$ touch src/templates/page.js
$ touch .env
$ tree -a -I 'node_modules|.cache|public'
.
├── .env
├── .gitignore
├── LICENSE
├── README.md
├── gatsby-browser.js
├── gatsby-config.js
├── gatsby-node.js
├── gatsby-ssr.js
├── package-lock.json
├── package.json
└── src
├── components
│   ├── header.js
│   ├── image.js
│   ├── layout.css
│   ├── layout.js
│   └── seo.js
├── images
│   ├── gatsby-astronaut.png
│   └── gatsby-icon.png
├── pages
│   └── 404.js
└── templates
└── page.js

GatsbyJSとWordPressを連携するために「gatsby-source-wordpress」というプラグインをインストールしています。

GatsbyJSにWordPress連携の設定を追加する

GatsbyJSにWordPress連携の設定を追加しましょう。

.env

API_PROTOCOL=http
API_URL=localhost:8080
JWT_USER=wordpress
JWT_PASSWORD=wordpress

gatsby-config.js

require('dotenv').config();
module.exports = {
siteMetadata: {
title: `Gatsby Default Starter`,
description: `Kick off your next, great Gatsby project with this default starter. This barebones starter ships with the main Gatsby configuration files you might need.`,
author: `@gatsbyjs`,
},
plugins: [
`gatsby-plugin-react-helmet`,
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: `${__dirname}/src/images`,
},
},
`gatsby-transformer-sharp`,
`gatsby-plugin-sharp`,
{
resolve: `gatsby-plugin-manifest`,
options: {
name: `gatsby-starter-default`,
short_name: `starter`,
start_url: `/`,
background_color: `#663399`,
theme_color: `#663399`,
display: `minimal-ui`,
icon: `src/images/gatsby-icon.png`,
},
},
/*
* Gatsby's data processing layer begins with “source”
* plugins. Here the site sources its data from WordPress.
*/
{
resolve: 'gatsby-source-wordpress',
options: {
baseUrl: process.env.API_URL,
protocol: process.env.API_PROTOCOL,
hostingWPCOM: false,
useACF: false,
acfOptionPageIds: [],
auth: {
jwt_user: process.env.JWT_USER,
jwt_pass: process.env.JWT_PASSWORD,
jwt_base_path: '/jwt-auth/v1/token',
},
verboseOutput: false,
perPage: 100,
concurrentRequests: 10,
includedRoutes: [
'**/*/*/categories',
'**/*/*/posts',
'**/*/*/pages',
'**/*/*/media',
'**/*/*/tags',
'**/*/*/taxonomies',
'**/*/*/users',
'**/*/*/menus',
],
excludedRoutes: [],
normalizer({ entities }) {
return entities;
},
},
},
],
};

GatsbyJSのGraphiQL画面でWordPress連携を確認する

GatsbyJSを起動します。

$ gatsby develop

ブラウザで「http://localhost:8000/___graphql」にアクセスし、GraphQLでWordPressのページ情報「allWordpressPage」とヘッダーメニュー情報「allWordpressWpApiMenusMenusItems」を取得できるか確認します。

NewImage

{
pages: allWordpressPage {
edges {
node {
id
link
status
template
title
content
slug
}
}
}
headerMenuItems: allWordpressWpApiMenusMenusItems(filter: {slug: {eq: "header-menu"}}) {
edges {
node {
name
slug
items {
title
object_slug
}
}
}
}
}

GatsbyJSとWordPressとの連携が確認できました。

GatsbyJSの画面を少し作る

WordPressから取得したデータを表示させるだけの簡単なフロントエンドをGatsbyJSで作ります。

gatsby-node.js

const path = require('path');
const slash = require('slash');
exports.createPages = async ({ graphql, actions }) => {
const { createPage, createRedirect } = actions;
createRedirect({
fromPath: '/',
toPath: '/welcome',
redirectInBrowser: true,
isPermanent: true,
});
const result = await graphql(`
{
allWordpressPage {
edges {
node {
id
link
status
template
title
content
slug
}
}
}
}
`);
if (result.errors) {
throw new Error(result.errors);
}
const { allWordpressPage } = result.data;
const pageTemplate = path.resolve(`./src/templates/page.js`);
allWordpressPage.edges.forEach(edge => {
createPage({
path: `/${edge.node.slug}/`,
component: slash(pageTemplate),
context: edge.node,
});
});
};

page.js

import React from 'react';
import Layout from '../components/layout';
import SEO from '../components/seo';
export default ({ pageContext }) => (
<Layout>
<SEO title={pageContext.title} />
<h1 dangerouslySetInnerHTML={{ __html: pageContext.title }} />
<div dangerouslySetInnerHTML={{ __html: pageContext.content }} />
</Layout>
);

header.js

import React from 'react';
import { graphql, StaticQuery, Link } from 'gatsby';
const Header = ({ siteTitle }) => (
<StaticQuery
query={graphql`
{
allWordpressWpApiMenusMenusItems(
filter: { slug: { eq: "header-menu" } }
) {
edges {
node {
name
slug
items {
title
object_slug
}
}
}
}
}
`}
render={props => (
<header
style={{
background: `rebeccapurple`,
marginBottom: `1.45rem`,
}}
>
<div
style={{
margin: `0 auto`,
maxWidth: 960,
padding: `1.45rem 1.0875rem`,
}}
>
<h1 style={{ margin: 0 }}>
<Link
to="/"
style={{
color: `white`,
textDecoration: `none`,
}}
>
{siteTitle}
</Link>
</h1>
<div style={{ marginTop: `10px` }}>
{props.allWordpressWpApiMenusMenusItems.edges[0].node.items.map(
item => (
<Link
style={{
color: `white`,
padding: `10px`,
}}
to={item.object_slug}
key={item.title}
>
{item.title}
</Link>
),
)}
</div>
</div>
</header>
)}
/>
);
export default Header;

簡単ですが完成です。

動作確認

最後にGatsbyJSを起動してWordPressから取得した情報が表示されることを確認しましょう。

まずはGatsbyJSを起動します。

$ gatsby develop

ブラウザで「http://localhost:8000/」にアクセスすると、「http://localhost:8000/welcome」にリダイレクトされ、WordPressから取得したWelcomeページが表示されました。

NewImage

「Test Page」のリンクをクリックすると、「http://localhost:8000/test」に遷移し、WordPressから取得したTestページが表示されました。

NewImage

OKですね。

最後に

いかがでしたか?これでGatsbyJSとWordPressのHeadless CMSのREST APIを連携させることができるようになったと思います。今回は取得まででしたが、JWT認証も設定しているので、記事の作成や更新もできます。試してみると面白いと思います。それでは。

環境

  • Docker: 18.09.2, build 6247962
  • NodeJS: v11.10.0
  • gatsby-cli: 2.4.11

カテゴリ : 技術 Tips & Tutorials タグ : gatsbyjs, headless-cms, jwt, rest-api, wordpress

2019年3月4日 By KD コメントを書く

Go言語のRESTアプリケーションを開発用にオートリロード可能な状態でDocker化する方法(fresh編)

Go言語のRESTアプリケーションを開発用にオートリロード可能な状態でDocker化する方法(fresh編)

Go言語によるRESTアプリケーションの開発を加速するために、Docker化し、freshでオートリロードを可能にする方法を紹介します。

はじめに

以前の記事でGo言語でRESTアプリケーションを実装する方法を紹介しました。今回は、開発環境を簡単に構築できるようにするために、Docker化し、その際に、「fresh」というオートリロード用のモジュールを導入する方法を紹介します。それでは、始めましょう。

前提

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

  • Go言語がインストールされていること
  • Dockerがインストールされていること
  • 以前の記事で実装したRESTアプリケーションが準備されていること(いつも通りチュートリアル形式で進めていきますが、内容を参考にするだけで良いなら不要です)

詳しくは「環境」を参照してください。

Go言語のRESTアプリケーションを開発用にオートリロード可能な状態でDocker化する

必要なファイルとフォルダを準備する

以前の記事で実装したRESTアプリケーションを準備して、そのプロジェクト内に必要なファイルとフォルダを作成します。

$ cd golang-rest-api/
$ touch Dockerfile.dev
$ touch docker-compose.yml
$ mkdir data
$ touch wait-for-it.sh

今回はローカルにPostgreSQLのデータを残すためにdataフォルダも作成します。また、PostgreSQLのデータベースが起動するのを待つために「wait-for-it」を使います。「wait-for-it.sh」にはGitHub上のスクリプトをコピペしてください。

Dockerファイルを作成する

Dockerファイル内でオートリロードのためのモジュール「fresh」を設定するようにします。

Dockerfile.dev

FROM golang:1.11.4-stretch
LABEL maintainer="YOU"
WORKDIR /app
COPY go.mod .
COPY go.sum .
RUN go mod download
RUN go get github.com/pilu/fresh
RUN go get -u github.com/go-delve/delve/cmd/dlv
COPY . .
EXPOSE 3000
CMD fresh main.go

Docker Composeファイルを作成する

docker-compose.yml

version: '3'
services:
app:
container_name: my_app
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "3000:3000"
volumes:
- .:/app
depends_on:
- db
command: >
bash -c "chmod +x ./wait-for-it.sh &&
./wait-for-it.sh db:5432 -t 30 -- fresh main.go"
db:
container_name: my_postgres
image: postgres:11.1
restart: always
environment:
- "TZ: Etc/UTC"
- POSTGRES_USER=puser
- POSTGRES_PASSWORD=ppassword
- POSTGRES_DB=testdb
ports:
- 5432:5432
volumes:
- ./data:/var/lib/postgresql/data
- ./initdb:/docker-entrypoint-initdb.d

RESTアプリケーション内のPostgreSQLの接続先をDocker Composeの設定に合わせて変更する

接続先を「localhost」から「my_postgres」に変更します。

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@my_postgres: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))
}

動作確認

では動作確認してみましょう。

まずは、Docker化したRESTアプリケーションが正しく動くか確認します。

$ docker-compose up -d
Creating network "golang-rest-api_default" with the default driver
Creating my_postgres ... done
Creating my_app      ... done
$ docker-compose ps
Name                  Command               State           Ports         
-----------------------------------------------------------------------------
my_app        bash -c chmod +x ./wait-fo ...   Up      0.0.0.0:3000->3000/tcp
my_postgres   docker-entrypoint.sh postgres    Up      0.0.0.0:5432->5432/tcp
$ 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
$ 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
$ 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
$ curl -v -H "Accept:application/json" http://localhost:3000/api/tweets | jq
...
[
{
"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
...
{
"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/num | jq
...
{
"message": "\"id\" is wrong"
}
(ここでソースコード上のエラーメッセージを変更する)
$ curl -v -H "Accept:application/json" http://localhost:3000/api/tweets/num | jq
...
{
"message": "\"id\" is absolutely wrong"
}

正しく動くことが確認できました。

おまけ

プロダクション用にDocker化する

Go言語はコンパイル言語なので、プロダクション用にはコンパイルして実行可能な状態にする必要があります。alpineで軽量のDockerイメージにする場合のDockerファイルは以下のようになります。

Dockerfile

FROM golang:1.11.4-stretch AS builder
WORKDIR /app
COPY go.mod .
COPY go.sum .
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o server
FROM alpine:3.8
LABEL maintainer="YOU"
WORKDIR /app
EXPOSE 3000
COPY --from=builder /app/server /app/
ENTRYPOINT ["/app/server"]

最後に

いかがでしたか?これでGo言語のRESTアプリケーションをDocker上で開発できるようになったことでしょう。それでは。

環境

  • Go: go1.11.4
  • Docker: 18.09.1, build 4c52b90
  • fresh: v0.0.0-20170301142741-9c0092493eff

カテゴリ : 技術 Tips & Tutorials タグ : auto-reload, docker, fresh, golang, hot-reload, rest-api

  • 1
  • 2
  • 次のページ »

ブログ更新情報や海外の関連情報などを配信する無料メルマガ

Sponsored Links

About Author

KD

世界を旅し日本を愛するエンジニア。大学でコンピュータサイエンスの楽しさを学び、日本の大手IT企業で働く中で、新しい技術やスケールするビジネスが北米にある事に気づく。世界に挑戦するための最大の壁が英語であったため、フィリピン留学およびカナダ留学を経て英語を上達させた。現在は日本在住でエンジニアとして働きつつ、次の挑戦に備えて世界の動向を注視している。挑戦に終わりはない。このブログでは、エンジニアやデザイナー向けの技術情報から、海外に留学したい人向けの留学情報、海外に興味がある人向けの海外旅行情報など、有益な情報を提供しています。

https://casualdevelopers.com/

最近の投稿

  • 2020年JS周辺のバックエンド寄りの注目技術!ネクストNodeJSの「Deno」と分散型パッケージレジストリの「Entropic」の紹介

    2020年JS周辺のバックエンド寄りの注目技術!ネクストNodeJSの「Deno」と分散型パッケージレジストリの「Entropic」の紹介

    2020年1月13日
  • 今さら聞けないJavaによる関数型プログラミング入門 ~ラムダ式、ストリーム、関数型インターフェース~

    今さら聞けないJavaによる関数型プログラミング入門 ~ラムダ式、ストリーム、関数型インターフェース~

    2019年11月4日
  • ReactのためのEslintおよびPrettierの設定方法 ~Airbnb JavaScript Style Guideの適用~

    ReactのためのEslintおよびPrettierの設定方法 ~Airbnb JavaScript Style Guideの適用~

    2019年10月30日
  • BashからZshに移行する方法(Mac編)

    BashからZshに移行する方法(Mac編)

    2019年10月21日
  • Create React Appを使わないでゼロからReactの開発環境を構築する方法(Webpack/Docker編)

    Create React Appを使わないでゼロからReactの開発環境を構築する方法(Webpack/Docker編)

    2019年9月30日

カテゴリ

  • 技術 Tips & Tutorials (100)
  • 技術塾 (6)
  • ライフハック (26)
  • 海外留学 (12)
  • 英語学習 (3)
  • コラム (6)

アーカイブ

最高の学習のために

人気記事ランキング

  • MySQLで「ERROR 2003 (HY000): Can't connect to MySQL server」と怒られた時の対処法
    MySQLで「ERROR 2003 (HY000): Can't connect to MySQL server」と怒られた時の対処法
  • Jupyter Notebookで「The kernel appears to have died. It will restart automatically.」というエラーが出た場合の原因と対処法
    Jupyter Notebookで「The kernel appears to have died. It will restart automatically.」というエラーが出た場合の原因と対処法
  • 爆速でJenkinsをマスターしよう(GitHubアカウント統合編) ~ JenkinsのGitHub Organizationの設定方法 ~
    爆速でJenkinsをマスターしよう(GitHubアカウント統合編) ~ JenkinsのGitHub Organizationの設定方法 ~
  • Expressで「Cannot set headers after they are sent to the client」と怒られた時の対処法
    Expressで「Cannot set headers after they are sent to the client」と怒られた時の対処法
  • SAKURAのメールボックスで独自ドメインのメールを設定し、Gmail経由で送受信する方法
    SAKURAのメールボックスで独自ドメインのメールを設定し、Gmail経由で送受信する方法
  • バンクーバー留学豆知識:バンクーバーのATMで日本の銀行のキャッシュカードを使ってお得にお金を引き出す方法
    バンクーバー留学豆知識:バンクーバーのATMで日本の銀行のキャッシュカードを使ってお得にお金を引き出す方法
  • [tips][perl] Perlで文字コードをいい感じに処理する方法
    [tips][perl] Perlで文字コードをいい感じに処理する方法
  • PythonでWebスクレイピング入門(Scrapy+Selenium編)
    PythonでWebスクレイピング入門(Scrapy+Selenium編)
  • Amazon EC2インスタンスにSSHできなくなった時の対処法
    Amazon EC2インスタンスにSSHできなくなった時の対処法
  • SpringBootのProfile毎にプロパティを使い分ける3つの方法
    SpringBootのProfile毎にプロパティを使い分ける3つの方法

Bitcoin寄付 / BTC Donation

Bitcoinを寄付しよう

BTC
Select Payment Method
Personal Info

Donation Total: BTC 0.0010

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

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

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

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

Copyright © 2023 KD - Casual Developers Notes