$30 off During Our Annual Pro Sale. View Details »

Building a web service. Part 4: Implement the s...

Building a web service. Part 4: Implement the server side

This is a project to build a web service for sharing indie anime.

The previous slide: https://speakerdeck.com/michaelfreling/building-a-web-service-upload-and-watch-a-video-by-firebase

Michael

June 24, 2022
Tweet

More Decks by Michael

Other Decks in Programming

Transcript

  1. Build a web service 3. Implement the server side with

    Next.js, Firebase, GraphQL, and MySQL
  2. Summary 1. Setup GraphQL and integrate MySQL with Prisma and

    PlanetScale 2. On a server side, use Firebase Auth and Cloud Storage with Firebase Admin 3. Update the logic of uploading and watching a video with a server side code
  3. Upload a video flow User Browser GraphQL Cloud Storage MySQL

    Upload a video Upload a video Upload the video Upload the video Submit Submit Send its metadata Send its metadata Fetch its metadata Fetch its metadata Metadata Metadata Update its metadata Update its metadata Store its metadata Store its metadata Respond with its video id Respond with its video id Show the video detail page Show the video detail page User Browser GraphQL Cloud Storage MySQL
  4. Watch a video flow User Browser GraphQL Cloud Storage MySQL

    Go to a video page Go to a video page Get video data Get video data Get video metadata by its id Get video metadata by its id Video metadata Video metadata Fetch the metadata of the video Fetch the metadata of the video Metadata Metadata Video metadata Video metadata Fetch a video file Fetch a video file Video Video Show a video detail page Show a video detail page User Browser GraphQL Cloud Storage MySQL
  5. Setup GraphQL, Prisma, and MySQL There are great examples in

    a Next.js official repository to set up GraphQL and Prisma GraphQL GraphQL Yoda TypeGraphQL MySQL integration by Prisma and PlanetScale Prisma PlanetScale
  6. Set up GraphQL in Next.js We just need to define

    a server in the /pages/api/{url} import { createServer } from "@graphql-yoga/node"; const server = createServer({ schema: { typeDefs, resolvers, }, endpoint: "/api/graphql", }); export default server;
  7. Setup GraphQL with TypeScript Use TypeGraphQL to define schema definitions

    and resolvers with TypeScript. The code like next for data types. @ObjectType() class Video { @Field((type) => ID) id: number; // ... } @InputType() class VideoInput { @Field() title: string; // ... }
  8. Setup GraphQL with TypeScript Resolver definition with TypeScript @Resolver(Video) class

    VideoResolver { @Query((returns) => Video) async video(@Arg("id", (type) => ID) id: string) { // ... } @Authorized() @Mutation((returns) => String) async submitVideo( @Arg("input") input: VideoInput, @Ctx("currentUser") currentUser: CurrentUser ): Promise<string> { // ... } }
  9. Setup GraphQL with TypeScript Use buildSchema for TypeGraphQL resolvers. import

    { buildSchema, } from "type-graphql"; const server = createServer<{ req: NextApiRequest; res: NextApiResponse; }>({ schema: await buildSchema({ resolvers: [VideoResolver], }), endpoint: "/api", });
  10. Set up Prisma and MySQL with Next.js There is an

    example of setting up Prisma with PlanetScale in Next.js repository MySQL integration by Prisma and PlanetScale Prisma PlanetScale Followed a few steps for the tutorial of PlanetScale to migrate a database.
  11. Set up Prisma and MySQL with Next.js There are a

    few things I needed to be careful to use Prisma With PlanetScale, the step to apply migration is different Just push a database and pull it to the branch When I tried to use prisma migrate dev --name init , there is an error which shadow DB cannot be created. Error: P3014 Prisma Migrate could not create the shadow database. // Original error: create database is not supported 0: migration_core::state::DevDiagnostic at migration-engine/core/src/state.rs:250
  12. Set up Prisma and MySQL with Next.js The table schemas

    are created with the definitions of Prisma models without any conversions In some cases, DB tables schemas are defined with plural and snake cases To change naming conventions of table schemas with models, use @@map and @map attribute descried here It seems there is no way to convert them automaticallly without defining the attributes
  13. Prisma definition enum VideoStatus { UNPUBLISHED @map("unpublished") PUBLISHED @map("published") }

    model Video { id Int @id @default(autoincrement()) userId String @map("user_id") @db.VarChar(64) status VideoStatus contentType String @map("content_type") @db.VarChar(64) filename String @db.VarChar(255) createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") @@map("videos") }
  14. Use Firebase Auth on the server side Send a token

    by an Authorization header with a Bearer authentication Run a verification check when const server = createServer<{ req: NextApiRequest; res: NextApiResponse; }>({ authChecker: async ({ root, args, context, info }) => { const token = context.request.headers.get("authorization"); if (token == null) { return false; } await auth().verifyIdToken(token.replace("Bearer ", "")); return true; },
  15. Update uploading a video 1. Upload a video file from

    a frontend to a Cloud Storage directly without the server 2. Send the metadata of the video to the server side and update it 1. It's required to verify whether it's the same between the file a user uploads originally and a filepath set by a user later 1. Use md5hash of the metadata of a GCS object 3. Store filename, user id and other metadata in MySQL
  16. Update uploading a video @Authorized() @Mutation((returns) => String) async submitVideo(

    @Arg("input") input: VideoInput, @Ctx("currentUser") currentUser: CurrentUser ): Promise<string> { const gcsFile = storage().bucket().file(path); const [metadata] = await gcsFile.getMetadata(); if (metadata.md5Hash != file.md5Hash) { throw new GraphQLYogaError(INTERNAL_SYSTEM_ERROR); } const video = await prisma.video.create({ data: { userId, filename: file.name, }, }); return String(video.id); }
  17. Watch a video @Query((returns) => Video) async video(@Arg("id", (type) =>

    ID) id: string) { const video = await prisma.video.findUnique({ where: { id: parsedId, }, }); // skip const storageRef = ref( getStorage(), `videos/${video.userId}/${video.filename}` ); const url = await getDownloadURL(storageRef) return { id: video.id, // }; }
  18. Next 1. Show the timeline of the videos 2. Update

    the video on a video edit page