【GraphQL】PostgresSQLからMongoDBへ乗り換える
GraphQL

【GraphQL】PostgresSQLからMongoDBへ乗り換える

作成日:2021年12月01日
更新日:2021年12月02日

前回は、会員登録機能とログイン機能を実装しました。

graphql-client-signin-signup

【GraphQL】フロントエンドで会員登録機能とログイン機能を実装する

今回は、バックエンドで PostgresSQL から MongoDB へ乗り換えます。

コードは、こちらです。

バックエンド:

MongoDB をセットアップする

まずは、MongoDB のセットアップから始めます。

セットアップが初めての方は、こちらで紹介しているので、ご確認ください。

MongoDB のダッシュボードの『Connect』をクリックします。

image2

Connect your application を選択しましょう。

image3

②Add your connection string into your application code の内容をコピーします。

image4

バックエンドの.env ファイルに移動して、DATABASE_URL のコードに貼り付けます。

DATABASE_URL="mongodb+srv://nao:<password>@cluster0.xks4y.mongodb.net/myFirstDatabase?retryWrites=true&w=majority"

パスワードを覚えている方は、<password>をパスワードに入れ替えます。

パスワードを忘れてしまった方は、MongoDB に戻って、Database Access をクリックします。

image5

ユーザー一覧の EDIT をクリックします。

image6

Edit Password をクリックして、新しいパスワードを作成します。

image7

Update User をクリックします。

image8

<password>を先程作成したパスワードに入れ替えます。

ts
DATABASE_URL =
"mongodb+srv://nao:XRyGxG74al8mqDbr@cluster0.xks4y.mongodb.net/myFirstDatabase?retryWrites=true&w=majority";

prisma の設定をする

prisma フォルダの schema.prisma を開きます。

generatorclientpreviewFeaturesを設定します。

previewFeaturesは、mongoDbを指定します。

generator client {
provider = "prisma-client-js"
previewFeatures = ["mongoDb"]
}

datasourcedbに設定しているprovidermongodbへ修正します。

datasource db {
provider = "mongodb"
url = env("DATABASE_URL")
}

PostgresSQL の仕様になっていた model を MongoDB の仕様に修正します。

例えば、idInt型でしたが、String型で@db.ObjectIdを追加します。

また、MongoDB の ID は、_idになるので、@map("_id")を指定しないといけません。

model Book {
id String @id @default(dbgenerated()) @map("_id") @db.ObjectId
title String
author String
isRead Boolean @default(false)
createdAt DateTime @default(now())
categoryId String @db.ObjectId
category Category @relation(fields: [categoryId], references: [id])
}
model Category {
id String @id @default(dbgenerated()) @map("_id") @db.ObjectId
name String
books Book[]
}
model User {
id String @id @default(dbgenerated()) @map("_id") @db.ObjectId
email String @unique
password String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

id の型を修正する

mongoDB の ID は ObjextId になるので、shema.ts の id に関する Int 型を ID 型へ修正します。

ts
const { gql } = require("apollo-server");
export const typeDefs = gql`
type Query {
books(isRead: Boolean): [Book!]!
book(id: ID!): Book
categories: [Category!]!
category(id: ID!): Category
}
type Mutation {
addBook(input: AddBookInput!): BookPayload!
deleteBook(id: ID!): BookPayload!
updateBook(id: ID!, input: UpdateBookInput!): BookPayload!
signup(email: String!, password: String!): AuthPayload!
signin(email: String!, password: String!): AuthPayload!
}
type Book {
id: ID!
title: String!
author: String!
createdAt: String!
category: Category!
isRead: Boolean!
}
type Category {
id: ID!
name: String!
books: [Book!]!
}
type User {
id: ID!
email: String!
password: String!
}
type Error {
message: String!
}
type BookPayload {
errors: [Error!]!
book: Book
}
type AuthPayload {
errors: [Error!]!
user: User
}
input BooksInput {
isRead: Boolean
}
input AddBookInput {
title: String!
author: String!
categoryId: ID!
isRead: Boolean!
}
input UpdateBookInput {
title: String
author: String
categoryId: ID
isRead: Boolean
}
`;

リゾルバーに設定しているidnumber型からstring型へ修正します。

Query.ts

ts
import { Context } from "../index";
export const Query = {
books: (_: any, { isRead }: { isRead: boolean }, { prisma }: Context) => {
return prisma.book.findMany({
where: {
isRead,
},
});
},
book: (_: any, { id }: { id: string }, { prisma }: Context) => {
return prisma.book.findUnique({
where: {
id,
},
});
},
categories: (_: any, __: any, { prisma }: Context) => {
return prisma.category.findMany();
},
category: (_: any, { id }: { id: string }, { prisma }: Context) => {
return prisma.category.findUnique({
where: {
id,
},
});
},
};

Book.ts

ts
import { Context } from "../index";
export const Book = {
category: (
{ categoryId }: { categoryId: string },
_: any,
{ prisma }: Context
) => {
return prisma.category.findUnique({
where: {
id: categoryId,
},
});
},
};

Category.ts

ts
import { Context } from "../index";
export const Category = {
books: ({ id }: { id: string }, _: any, { prisma }: Context) => {
return prisma.book.findMany({
where: {
categoryId: id,
},
});
},
};

Mutation.ts

ts
import { Book, Category, User } from "@prisma/client";
import validator from "validator";
import bcrypt from "bcryptjs";
import { Context } from "../index";
type MutationBook = {
input: {
title: string;
author: string;
categoryId: string;
isRead: boolean;
};
};
type MutationUser = {
email: string;
password: string;
};
type BookPayload = {
errors: {
message: string;
}[];
book: Book | null;
};
type UserPayload = {
errors: {
message: string;
}[];
user: User | null;
};
export const Mutation = {
addBook: async (
_: any,
{ input }: MutationBook,
{ prisma }: Context
): Promise<BookPayload> => {
const { title, author, categoryId, isRead } = input;
if (!title || !author || !categoryId || !isRead) {
return {
errors: [
{
message: "本の内容を入力してください",
},
],
book: null,
};
}
const newBook = await prisma.book.create({
data: {
title,
author,
categoryId,
isRead,
},
});
return {
errors: [],
book: newBook,
};
},
deleteBook: async (
_: any,
{ id }: { id: string },
{ prisma }: Context
): Promise<BookPayload> => {
const book = await prisma.book.findUnique({
where: {
id,
},
});
if (!book) {
return {
errors: [
{
message: "本のデータがありません",
},
],
book: null,
};
}
await prisma.book.delete({
where: {
id,
},
});
return {
errors: [],
book,
};
},
updateBook: async (
_: any,
{ id, input }: { id: string; input: MutationBook["input"] },
{ prisma }: Context
): Promise<BookPayload> => {
const book = await prisma.book.findUnique({
where: {
id,
},
});
if (!book) {
return {
errors: [
{
message: "本のデータがありません",
},
],
book: null,
};
}
const updateBooks = await prisma.book.update({
data: {
...input,
},
where: {
id,
},
});
return {
errors: [],
book: updateBooks,
};
},
signup: async (
_: any,
{ email, password }: MutationUser,
{ prisma }: Context
): Promise<UserPayload> => {
const isEmail = validator.isEmail(email);
if (!isEmail) {
return {
errors: [
{
message: "emailが正しくありません",
},
],
user: null,
};
}
const isPassword = validator.isLength(password, {
min: 4,
});
if (!isPassword) {
return {
errors: [
{
message: "4文字上のパスワードを入力してください",
},
],
user: null,
};
}
const hashedPassword = await bcrypt.hash(password, 10);
const newUser = await prisma.user.create({
data: {
email,
password: hashedPassword,
},
});
return {
errors: [],
user: newUser,
};
},
signin: async (
_: any,
{ email, password }: MutationUser,
{ prisma }: Context
): Promise<UserPayload> => {
const user = await prisma.user.findUnique({
where: {
email,
},
});
if (!user) {
return {
errors: [
{
message: "アカウント情報が間違っています",
},
],
user: null,
};
}
const comparePassword = await bcrypt.compare(password, user.password);
if (!comparePassword) {
return {
errors: [
{
message: "パスワードが間違っています",
},
],
user: null,
};
}
return {
errors: [],
user,
};
},
};

カテゴリの mutation を設定する

バックエンドからカテゴリを追加したいので、スキーマとリゾルバを作成します。

schema.ts

ts
const { gql } = require("apollo-server");
export const typeDefs = gql`
type Query {
books(isRead: Boolean): [Book!]!
book(id: ID!): Book
categories: [Category!]!
category(id: ID!): Category
}
type Mutation {
addBook(input: AddBookInput!): BookPayload!
deleteBook(id: ID!): BookPayload!
updateBook(id: ID!, input: UpdateBookInput!): BookPayload!
addCategory(name: String!): CategoryPayload!
signup(email: String!, password: String!): AuthPayload!
signin(email: String!, password: String!): AuthPayload!
}
type Book {
id: ID!
title: String!
author: String!
createdAt: String!
category: Category!
isRead: Boolean!
}
type Category {
id: ID!
name: String!
books: [Book!]!
}
type User {
id: ID!
email: String!
password: String!
}
type Error {
message: String!
}
type BookPayload {
errors: [Error!]!
book: Book
}
type CategoryPayload {
errors: [Error!]!
category: Category
}
type AuthPayload {
errors: [Error!]!
user: User
}
input BooksInput {
isRead: Boolean
}
input AddBookInput {
title: String!
author: String!
categoryId: ID!
isRead: Boolean!
}
input UpdateBookInput {
title: String
author: String
categoryId: ID
isRead: Boolean
}
`;
ts
import { Book, Category, User } from "@prisma/client";
import validator from "validator";
import bcrypt from "bcryptjs";
import { Context } from "../index";
type MutationBook = {
input: {
title: string;
author: string;
categoryId: string;
isRead: boolean;
};
};
type MutationCategory = {
name: string;
};
type MutationUser = {
email: string;
password: string;
};
type BookPayload = {
errors: {
message: string;
}[];
book: Book | null;
};
type CategoryPayload = {
errors: {
message: string;
}[];
category: Category | null;
};
type UserPayload = {
errors: {
message: string;
}[];
user: User | null;
};
export const Mutation = {
addBook: async (
_: any,
{ input }: MutationBook,
{ prisma }: Context
): Promise<BookPayload> => {
const { title, author, categoryId, isRead } = input;
if (!title || !author || !categoryId || !isRead) {
return {
errors: [
{
message: "本の内容を入力してください",
},
],
book: null,
};
}
const newBook = await prisma.book.create({
data: {
title,
author,
categoryId,
isRead,
},
});
return {
errors: [],
book: newBook,
};
},
deleteBook: async (
_: any,
{ id }: { id: string },
{ prisma }: Context
): Promise<BookPayload> => {
const book = await prisma.book.findUnique({
where: {
id,
},
});
if (!book) {
return {
errors: [
{
message: "本のデータがありません",
},
],
book: null,
};
}
await prisma.book.delete({
where: {
id,
},
});
return {
errors: [],
book,
};
},
updateBook: async (
_: any,
{ id, input }: { id: string; input: MutationBook["input"] },
{ prisma }: Context
): Promise<BookPayload> => {
const book = await prisma.book.findUnique({
where: {
id,
},
});
if (!book) {
return {
errors: [
{
message: "本のデータがありません",
},
],
book: null,
};
}
const updateBooks = await prisma.book.update({
data: {
...input,
},
where: {
id,
},
});
return {
errors: [],
book: updateBooks,
};
},
addCategory: async (
_: any,
{ name }: MutationCategory,
{ prisma }: Context
): Promise<CategoryPayload> => {
if (!name) {
return {
errors: [
{
message: "カテゴリを入力してください",
},
],
category: null,
};
}
const newCategory = await prisma.category.create({
data: {
name,
},
});
return {
errors: [],
category: newCategory,
};
},
signup: async (
_: any,
{ email, password }: MutationUser,
{ prisma }: Context
): Promise<UserPayload> => {
const isEmail = validator.isEmail(email);
if (!isEmail) {
return {
errors: [
{
message: "emailが正しくありません",
},
],
user: null,
};
}
const isPassword = validator.isLength(password, {
min: 4,
});
if (!isPassword) {
return {
errors: [
{
message: "4文字上のパスワードを入力してください",
},
],
user: null,
};
}
const hashedPassword = await bcrypt.hash(password, 10);
const newUser = await prisma.user.create({
data: {
email,
password: hashedPassword,
},
});
return {
errors: [],
user: newUser,
};
},
signin: async (
_: any,
{ email, password }: MutationUser,
{ prisma }: Context
): Promise<UserPayload> => {
const user = await prisma.user.findUnique({
where: {
email,
},
});
if (!user) {
return {
errors: [
{
message: "アカウント情報が間違っています",
},
],
user: null,
};
}
const comparePassword = await bcrypt.compare(password, user.password);
if (!comparePassword) {
return {
errors: [
{
message: "パスワードが間違っています",
},
],
user: null,
};
}
return {
errors: [],
user,
};
},
};

エラーを識別するための!isRead は、false にすると引っかかってしまうので削除しておきます。

ts
addBook: async (
_: any,
{ input }: MutationBook,
{ prisma }: Context
): Promise<BookPayload> => {
const { title, author, categoryId, isRead } = input;
if (!title || !author || !categoryId) {
return {
errors: [
{
message: "本の内容を入力してください",
},
],
book: null,
};
}
const newBook = await prisma.book.create({
data: {
title,
author,
categoryId,
isRead,
},
});
return {
errors: [],
book: newBook,
};
},

一通り完成したので、バックエンドのサーバーを起動してみます。

image9

問題なく、起動できました。

では、Apollo Studio で動作確認しましょう。

まずは、Category を作成します。

graphql
mutation AddCategory($name: String!) {
addCategory(name: $name) {
errors {
message
}
category {
name
}
}
}

Variables に値を入力します。

image10

『AddCategory』ボタンをクリックすると、

image11

status 200 が返ってきて、データが登録することができていそうです。

MongoDB を確認すると、

image12

Category のデータが追加されていました。

次は、Book の登録を行います。

再び、Apollo Studio に戻って、Book を追加するために GraphQL を作成します。

graphql
mutation AddBook($input: AddBookInput!) {
addBook(input: $input) {
errors {
message
}
book {
title
author
category {
name
}
isRead
}
}
}

Variables に指定する categoryId を調べます。

Category 一覧を取得してみましょう。

graphql
query Categories {
categories {
id
name
}
}

image13

一覧で取得することができた id の値をコピーして、categoryId の値に貼り付けます。

image14

AddBook をクリックすると、

image15

Book のデータを追加することができました。

MongoDB を確認すると、

image16

無事、データが反映されていました。

全文は、こちらです。

次回は、バックエンドでページネーションを追加します。

graphql-backend-pagination

【GraphQL】バックエンドでページネーションを追加する

© 2024あずきぱんウェブスタジオ