VienDinhCom/next-full-stack

How to Upload Files with Soki in Next.js

VienDinhCom opened this issue · 0 comments

Uploading files is one of the most important tasks while we're working on a web project. So, in the article, I will show you how I upload files with soki.

Schemas

File Schema

In this section, I will create a function type called upload. And I will put this function type into a child schema called FileSchema. The hello function will have the input and the output like the code below.

// src/shared/schemas/file.schema.ts

import { createSchema, fn, z } from 'soki';

export const FileSchema = createSchema({
  upload: fn({
    input: {
      file: z.file(),
    },
    output: z.string(), // file name
  }),
});

Root Schema

Next, I will put the FileSchema into the RootSchema like this.

// src/shared/schemas/root.schema.ts

import { createRootSchema } from 'soki';

import { FileSchema } from './file.schema';
import { MessageSchema } from './message.schema';

export const RootSchema = createRootSchema({
  file: FileSchema,
  message: MessageSchema,
});

On Server

File Resolver

This step, I will implement FileSchema with the Resolvers['file'] type. The upload function will return the name of the file I'm going to upload.

// src/backend/resolvers/file.resolver.ts

import { Resolvers, createResolver } from '@backend/core';

export const FileResolver = createResolver<Resolvers['file']>({
  upload: async ({ file }, context) => {
    return file.name;
  },
});

Root Resolver

Next, I will put the FileResolver into the RootResolver like this.

// src/backend/resolvers/root.resolver.ts

import { Context, createRootResolver } from '@backend/core';
import { RootSchema } from '@shared/schemas/root.schema';

import { FileResolver } from './file.resolver';
import { MessageResolver } from './message.resolver';

export const RootResolver = createRootResolver({
  RootSchema,
  resolvers: {
    file: FileResolver,
    message: MessageResolver,
  },
  context: async (req, res): Promise<Context> => {
    return {};
  },
});

On Client

Finally, I will create a form and an upload function. So I can call the upload function from the FileResolver.

// src/pages/upload.tsx

import { useState } from 'react';
import { DefaultLayout } from '@frontend/components/Layouts/DefaultLayout';
import { ApiService, useMuation, getFiles, File } from '@frontend/services/api.service';

export default function Page() {
  const [file, setFile] = useState<File>();

  const { loading, data, error, mutate } = useMuation(async (file: File) => {
    return ApiService.file.upload({ file });
  });

  return (
    <DefaultLayout>
      <h1>Upload Files</h1>
      <form
        onSubmit={(event) => {
          event.preventDefault();
          if (file) mutate(file);
        }}
      >
        <input
          type="file"
          onChange={(event) => {
            const files = getFiles(event.currentTarget.files);
            setFile(files[0]);
          }}
        />
        <br />
        {error && <p>Error: {error.message}</p>}
        {data && (
          <p>
            <b>{data}</b> is uploaded!
          </p>
        )}
        <p>
          <button type="submit">{loading ? 'Uploading...' : 'Upload Now!'}</button>
        </p>
      </form>
    </DefaultLayout>
  );
}

To test the result, please run yarn dev and go to http://localhost:3000/upload to see how it works.

Demo: https://next-full-stack-git-issue-5.maxvien.vercel.app/upload

Source Code

You can find the source code of this tutorial in this branch: https://github.com/Maxvien/next-full-stack/tree/issue-5