[Node.js] S3からjsonをdownload

$ npm install aws-sdk

s3.js

const express = require('express')
const AWS = require('aws-sdk')
var fs = require('fs')

const app = express()

const s3Client = new AWS.S3({
	accessKeyId: '',
	secretAccessKey: '',
	region: 'ap-northeast-1'
})

const params = {
	Bucket: 'hogehoge',
	Key: 'address.json',
}

s3Client.getObject(params, function(err, data){
	if(err){
		console.log(err, err.stack);
	} else {
		var object = JSON.parse(data.Body.toString());
		fs.writeFile('hoge.json', JSON.stringify(object), function(err, result){
			if(err) console.log('error', err);
		});
	}
})


const jsonObject = JSON.parse(fs.readFileSync('hoge.json', 'utf8'));
console.log(jsonObject);


app.listen(8000, () => console.log('Server Now Running'));

これをgraphQLに応用すると、mysqlから取得した場合も、connection.queryで一時保存し、jsonを読み込んでfilterで処理できますね。ちょっと冗長ではありますが。

  connection.query(
    'SELECT * FROM master_zip',
    (error, results) => {
      var object = JSON.parse(JSON.stringify(results));
      fs.writeFile('address.json', JSON.stringify(object), function(err, result){
        if(err) console.log('error', err);
      });
    }
);

var getAddress = function(args){

	var zip_code = args.zip_code;
  const jsonObject = JSON.parse(fs.readFileSync('address.json', 'utf8'));

  return jsonObject.filter(address => {
      return address.zip_code == zip_code;
  })[0];
}

[Node.js] GraphQLで更新する

var schema = buildSchema(`
	type Query {
		course(id: Int!): Course
		courses(topic: String):[Course]
	},
	type Mutation {
		updateCourseTopic(id: Int!, topic: String!): Course
	}
	type Course {
		id: Int
		title: String
		author: String
		description: String
		topic: String
		url: String
	}
`);

// 省略

var updateCourseTopic = function({id, topic}){
	coursesData.map(course => {
		if(course.id === id){
			course.topic = topic;
			return course;
		}
	});
	return coursesData.filter(course => course.id === id)[0];
}
var root = {
	course: getCourse,
	courses: getCourses,
	updateCourseTopic: updateCourseTopic
};

$ node server2.js

mutation updateCourseTopic($id: Int!, $topic: String!) {
  updateCourseTopic(id: $id, topic: $topic) {
    ... courseFields 
  }
}
fragment courseFields on Course {
 title
 author
 description
 topic
 url
}

{
 "id": 1,
 "topic": "JavaScript"
}

何となく使い方はわかりました。

Node.jsでGraphQLを動かす

$ npm init
$ npm install graphql express express-graphql -save

server.js

var express = require('express');
var express_graphql = require('express-graphql').graphqlHTTP;
var { buildSchema } = require('graphql');

var schema = buildSchema(`
	type Query {
		message: String
	}
`);

var root = {
	message: () => 'Hello World!'
};

var app = express();
app.use('/graphql', express_graphql({
 schema: schema,
 rootValue: root,
 graphiql: true
}));
app.listen(8000, () => console.log('Express GraphQL Server Now Running On 192.168.34.10:8000/graphql'))

{
	message
}
{
  "data": {
    "message": "Hello World!"
  }
}

$ curl -XPOST -H “Content-Type:application/json” ‘http://192.168.34.10:8000/graphql’ -d ‘{“query”: “query { message }”}’
{“data”:{“message”:”Hello World!”}}
L jsonでクエリをリクエストすると返ってくる

### パラメータを受け取る

var express = require('express');
var express_graphql = require('express-graphql').graphqlHTTP;
var { buildSchema } = require('graphql');

var schema = buildSchema(`
	type Query {
		course(id: Int!): Course
		courses(topic: String):[Course]
	},
	type Course {
		id: Int
		title: String
		author: String
		description: String
		topic: String
		url: String
	}
`);
var coursesData = [
{
	id: 1,
	title: '初めてのGraphQL ―Webサービスを作って学ぶ新世代API',
	author: 'Eve Porcello',
	description: '本書で紹介するGraphQLは2015年にFacebookによって公開されたRESTとは異なるアプローチのアーキテクチャです。',
	topic: 'GraphQL',
	url: 'https://www.amazon.co.jp/%E5%88%9D%E3%82%81%E3%81%A6%E3%81%AEGraphQL-%E2%80%95Web%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%82%92%E4%BD%9C%E3%81%A3%E3%81%A6%E5%AD%A6%E3%81%B6%E6%96%B0%E4%B8%96%E4%BB%A3API-Eve-Porcello/dp/487311893X/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&dchild=1&keywords=graphql&qid=1633041782&sr=8-1'
},
{
	id: 2,
	title: '基礎からはじめるGraphQL',
	author: '志村翔太',
	description: '本書ではGraohQLの基礎文法や概念を学び、実際にGraohQLを利用したアプリケーションの完成を目指していきます。',
	topic: 'GraphQL',
	url: 'https://www.amazon.co.jp/%E5%9F%BA%E7%A4%8E%E3%81%8B%E3%82%89%E3%81%AF%E3%81%98%E3%82%81%E3%82%8BGraphQL-%E5%BF%97%E6%9D%91%E7%BF%94%E5%A4%AA-ebook/dp/B08PC8H5HF/ref=sr_1_2?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&dchild=1&keywords=graphql&qid=1633071074&sr=8-2'
},
{
	id: 3,
	title: 'Node.js超入門 第3版',
	author: '掌田津耶乃',
	description: 'Node.jsの入門者向け書籍です。2018/8に出た「Node.js超入門 第2版」の改訂版です。改訂内容は新バージョン14対応、データベースはSQLite3、ORMはSequelizeに変更しています。CSS関連はBootstrap利用、Expressは最初からGeneratorを使う形で解説しています。',
	topic: 'NodeJS',
	url: 'https://www.amazon.co.jp/Node-js%E8%B6%85%E5%85%A5%E9%96%80-%E7%AC%AC3%E7%89%88-%E6%8E%8C%E7%94%B0%E6%B4%A5%E8%80%B6%E4%B9%83/dp/479806243X/ref=sr_1_1_sspa?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&dchild=1&keywords=nodejs&qid=1633071225&sr=8-1-spons&psc=1&spLa=ZW5jcnlwdGVkUXVhbGlmaWVyPUEyNEMxVzdLVUtWOU1ZJmVuY3J5cHRlZElkPUEwNjIwNjAzMkhaN1dYSzZVSEw2NiZlbmNyeXB0ZWRBZElkPUExSkVDRkoyVzMwTlBBJndpZGdldE5hbWU9c3BfYXRmJmFjdGlvbj1jbGlja1JlZGlyZWN0JmRvTm90TG9nQ2xpY2s9dHJ1ZQ=='
}
]
var getCourse = function(args){
	var id = args.id;
	return coursesData.filter(course => {
		return course.id == id;
	})[0];
}
var getCourses = function(args){
	if(args.topic){
		var topic = args.topic;
		return coursesData.filter(course = course.topic === topic);
	} else {
		return coursesData;
	}
}
var root = {
	course: getCourse,
	courses: getCourses
};

var app = express();
app.use('/graphql', express_graphql({
 schema: schema,
 rootValue: root,
 graphiql: true
}));
app.listen(8000, () => console.log('Express GraphQL Server Now Running On 192.168.34.10:8000/graphql'))
query getSingleCourse($courseID: Int!) {
 course(id: $courseID) {
  title
  author
  description
  topic
  url
 }
}

{ 
 "topic":"Node.js"
}

$ curl -XPOST -H “Content-Type:application/json” ‘http://192.168.34.10:8000/graphql’ -d ‘{“query”: “query getSingleCourse($courseID:Int!){course(id:$courseID){title author description topic url}}”, “variables”: {“courseID”:1}}’
{“data”:{“course”:{“title”:”初めてのGraphQL ―Webサービスを作って学ぶ新世代API”,”author”:”Eve Porcello”,”description”:”本書で紹介するGraphQLは2015年にFacebookによって公開されたRESTとは異なるアプローチのアーキテクチャです。”,”topic”:”GraphQL”,”url”:”https://www.amazon.co.jp/%E5%88%9D%E3%82%81%E3%81%A6%E3%81%AEGraphQL-%E2%80%95Web%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%82%92%E4%BD%9C%E3%81%A3%E3%81%A6%E5%AD%A6%E3%81%B6%E6%96%B0%E4%B8%96%E4%BB%A3API-Eve-Porcello/dp/487311893X/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&dchild=1&keywords=graphql&qid=1633041782&sr=8-1″}}}

query getCourses($topic: String!) {
 courses(topic: $topic) {
  title
  url
 }
}

{
  "topic": "GraphQL"
}

{
“errors”: [
{
“message”: “course is not defined”,
“locations”: [
{
“line”: 2,
“column”: 2
}
],
“path”: [
“courses”
]
}
],
“data”: {
“courses”: null
}
}

なんやろう、ようわからんね

GraphQLをより理解する[PHP編]

### Resolver
The resolver is basically a call back function for each field. There is always a default resolver for all fields, when we define own resolve function for a field, we simply override the default resolver.

### How to define Schema for Query

$schema_obj = new Schema {
	"query" => $queryType,
	"mutation" => $mutationType,
	"subscription" => $subscriptionType
}

index.php

require_once __DIR__ . '/vendor/autoload.php';

use GraphQL\GraphQL;
use GraphQL\Type\Schema;
try {
	$schema_obj = new Schema([
		'query' => $queryType
	]);
	$Input = file_get_contents('php://input');
    $input_data = json_decode($Input, true);
    $query_data = $input_data['query'];
    $variable_values = isset($input_data['variables']) ? $input_data['variables'] : null;
    $value = ['prefix' => 'Output: '];
    $resultant = GraphQL::executeQuery($schema_obj, $query_data, $value, null, $variable_values);
    $output_value = $resultant->toArray();
} catch(\Exception $e){
	$output_value = [
		'errors' => [
			[
				'message' => $e->getMessage()
			]
		]
	];
}
header('Content-Type: application/json');
echo json_encode($output_value);

$ curl http://192.168.34.10:8000 -d ‘{“query”: “query { echo(message: \” Hi Knowband, this is my first GraphQL program \”) }” }’
{“errors”:[{“message”:”Schema does not define the required query root type.”,”extensions”:{“category”:”graphql”},”locations”:[{“line”:1,”column”:1}]}]}

うむ… 実践的にやらないとあかんな

PHPでGraphQLを試したい

まずubuntuにcomposerを入れます。

$ curl -sS https://getcomposer.org/installer | php
$ sudo mv composer.phar /usr/local/bin/composer
$ sudo chmod +x /usr/local/bin/composer
$ source ~/.bashrc
$ composer -v
Composer version 2.1.8 2021-09-15 13:55:14

### ライブラリのインストール
$ composer require webonyx/graphql-php

public/graphql/index.php

require_once __DIR__ . '/../../vendor/autoload.php';

use GraphQL\Type\Definition\ObjectType;

class Query extends ObjectType {
	public function __contruct(){
		parent::__construct([
			'name' => 'Query',
			'fields' => [
				'number' => [
					'type' => Type::int(),
					'args' => [
						'number' => Type::int(),
					],
					'resolve' => function($value, $args, $context, ResolveInfo $resolveInfo){
						return $args['number'];
					}
				],
			],
		]);
	}
}

$schema = new GraphQL\Type\Schema([
	'query' => new Query(),
]);

$server = new Graph\Server\StandardServer([
	'schema' => $schema
]);

$ php -S 192.168.34.10:8000
$ curl -X POST -H “Content-Type: application/json” “http://192.168.34.10:8000/graphql/” \
> -d ‘{“query”: “query { number(number: 20210928) }”}’

### Repositoryを使う
src/Type/User/User.php

namespace Hpscript\Type\User;

use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use Hpscript\Repository\UserRepository;
use Hpscript\Type\Definition\DomainType;

class User extends ObjectType {

	private $userRepository;

	public function __construct(){
		$this->userRepository = new UserRepository;
		parent::__construct([
			'name' => 'User',
			'fields' => function(){
				return [
					'id' => [
						'type' => Type::int(),
						'resolve' => function($id){
							return $id;
						},
					],
					'name' => [
						'type' => Type::string(),
						'resolve' => function($id){
							return $this->getUser($id)->getName();
						}
					],
					'profile' => [
						'type' => Type::string(),
						'resolve' => function($id){
							return $this->getUser($id)->getProfile();
						}
					],
					'address' => [
						'type' => Type::string(),
						'resolve' => function($id){
							return $this->getUser($id)->getName();
						}
					],
				];
			},
		]);
	}
}

private function getUser($id){
	return $this->userRepository->getUser($id);
}

src/Type/Definition/DomainType.php

namespace Hpscript\Type\Definition;

use Hpscript\Type\User\User;

class DomainType {

	private static $user;

	public static function user(){
		if(!isset(static::$user)){
			static::$user = new User();
		}
		return static::$user;
	}
}

src/Repository/UserRepository.php

namespace Hpscript\Repository;

class UserRepository {

	private $dummyData;

	public function __construct(){
		$sakamoto = new User(1, 'Sakamoto', '坂本太郎', '東京都');
		$sato = new User(2, 'Sato', '佐藤和子', '神奈川');
		$tanaka = new User(3, 'Tanaka', '田中はじめ', '愛知県');
		$this->dummyData = [
			1 => $sakamoto,
			2 => $sato,
			3 => $tanaka
		];
	}

	public function getUser($id){
		data_default_timezone_set('Asia/Tokyo');
		error_log(date('Y-m-d H:i:s') . "\t" . __METHOD__ . "\n", 3, "/tmp/php-graphql-sample.log");
		return $this->dummyData[$id];
	}
}

class User {
	private $id;
	private $name;
	private $profile;
	private $address;

	public function __construct($id, $name, $profile, $address){
		$this->id = $id;
		$this->name = $name;
		$this->profile = $profile;
		$this->address = $address;
	}

	public function getId(){
		return $this->id;
	}
	public function getName(){
		return $this->name;
	}
	public function getProfile(){
		return $this->profile;
	}
	public function getAddress(){
		return $this->address;
	}
}

public/grapql/index.php

require_once __DIR__ . '/../../vendor/autoload.php';

use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\ResolveInfo;
use Hpscript\Type\Definition\DomainType;

class Query extends ObjectType {
	public function __contruct(){
		parent::__construct([
			'name' => 'Query',
			'fields' => [
				'user' => [
					'type' => DomainType::user(),
					'args' => [
						'id' => Type::int(),
					],
					'resolve' => function($value, $args, $context, ResolveInfo $resolveInfo){
						return $args['id'];
					}
				],
			],
		]);
	}
}

$schema = new GraphQL\Type\Schema([
	'query' => new Query(),
]);

$server = new Graph\Server\StandardServer([
	'schema' => $schema
]);

$server->handleRequest();

composer.json

{
	"autoload": {
		"psr-4": {
			"Hpscript\\": "src"
		}
	},
    "require": {
        "webonyx/graphql-php": "^14.9"
    }
}

$ curl -X POST -H “Content-Type: application/json” “http://192.168.34.10:8000/graphql/” -d ‘{“query”: “query { user(id: 2){id name} }”}
POST /graphql/ – Uncaught ArgumentCountError: Too few arguments to function GraphQL\Type\Definition\ObjectType::__construct(), 0 passed in /home/vagrant/dev/graphql/public/graphql/index.php on line 30 and exactly 1 expected in /home/vagrant/dev/graphql/vendor/webonyx/graphql-php/src/Type/Definition/ObjectType.php:86
エラーになるな。mutationはいらないから、querytypeだけで良いような気がするんだが。。

[GraphQL]入門

フロントエンドエンジニアの間ではGraphQLはもはや必須スキル?
ということでGraphQLを入門

### GraphQLとは
– APIのためのクエリ言語(Graph Query Language)
– SQLのようなクエリでAPIを定義する
– REST APIとはエンドポイントが異なる(特定の値を取得するために、余分なデータも取得されてしまう、あるいは、1つのエンドポイントでは必要なデータが取得できない といった問題を解決できる 必要なデータだけ要求する)
– 型付スキーマで記述する
– GraphQLのSubscriptionsはWebSocketを使用しているので、簡単にリアルタイム通信ができる
– Client側がGraphQL Query Languageで、Server側がGraphQL Schema Language

注) サーバ側とクライアント側 両方を学ぶ必要がある

### GraphQL Query Language(GET)
Githubの例: https://docs.github.com/en/graphql/overview/explorer

query { 
  viewer { 
    login
  }
}
query { 
  user(login: "hpscript"){
    id
    name
    url
  }
}

おおおお、これは凄い
POST, PUT, DELETEはmutationを使用する

“!”は必須という意味

type User {
	login: String!
	id: String!
	name: String!
	age: Int
	bio: String
	url: String
}

実際に動かしてみないとわからないね