1.2.2 • Published 2 months ago

@artempoletsky/easyrpc v1.2.2

Weekly downloads
-
License
ISC
Repository
-
Last release
2 months ago

About The Project

EasyRPC is a library for implementing Remote Procedure Call pattern in Typescript.

Key features:

  • Zero learning curve. If you know how to use Javascript asynchronous functions you know how to use this library.
  • Write methods on the server and call them on the client like AJAX doesn't even exist.
  • Minimum boilerplate.
  • Convenient error handling.
  • Supports templates for error messages and i18n.

It uses zod for data validation.

EasyRPC is framework agnostic but it has some useful methods for working with Next.js and Mantine.

Installation

npm install --save @artempoletsky/easyrpc

Basic usage

Thess examples are provided for a Next.js route

First create a new directory for a new Next API route. Create 2 files inside it: schemas.ts and route.ts.

schemas.ts contains validation rules for your API methods.

It's recommended to create it if you want to use zod for validation on the client. This way you can use same validation rules on both the client and the server.

// schemas.ts

import z from "zod";

//myMethod is exact name of your method
export const myMethod = z.object({
  num: z.number(),
  num: z.string(),
  array: z.array(z.number()),
  etc: z.any(),
});

// the naming prefix `A` stands for Argument. You can use your own naming conventions.
export type AMyMethod = z.infer<typeof myMethod>;

// ... export other methods this way

Next.js route:

// route.ts

import { NextResponse } from "next/server";
import validate, { NextPOST, ResponseError } from "@artempoletsky/easyrpc";
import * as schemas from "./schemas";
import type { AMyMethod } from "./schemas";

//implement your method 
async function myMethod({ num, str, array, etc }: AMyMethod){
  // zod will ensure that all args are valid here

  // send 400 error to the client
  if (false) throw new ResponseError("Bad request");

  // send 400 error to the client and tell that a specific form field caused it
  if (false) throw new ResponseError("fieldName", "Bad field");

  if (false) throw new Error("Something got wrong"); // 500 error

  return "Hello RPC!"; // send this as a responce to the client
}

// export the method signature as a type for using on the client
// the naming prefix `F` stands for Function
export type FMyMethod = typeof myMethod;

//  the shortcut `NextPOST` function that creates a POST function for you
export const POST = NextPOST(NextResponse, schemas, {
  myMethod,
});

Also you can implement the POST method yourself if you want a more fine tuned behavior. Use validate function for that.

import validate from "@artempoletsky/easyrpc";

// next.js API route implementation
export async function POST(req: NextRequest) {
  // get method and arguments from the request
  let { method, args } = await req.json();


  let [result, statusObject] = await validate(
    { // pass the arguments and the method name
      method, // "myMethod"
      args, // args is TMyMethod if valid
    },
    schemas,
    {
      myMethod, // pass here all of your methods 
    }
  );
  // `result`` is "Hello RPC!" if args is valid
  // `statusObject` is { status: 200 } if args is valid
  // if the method name doesn't exist or `args` is invalid `validate` will return 400 and the error message
  return NextResponse.json(result, statusObject);
}

The client code:

// some_client_code.tsx

import { getAPIMethod } from "@artempoletsky/easyrpc/client";
import type { FMyMethod } from "../path/to/your/api/route";

const myMethod = getAPIMethod<FMyMethod>("http_path/to/your_route", "myMethod");

export default function Page() {
  const [setRequestError, mainErrorMessage] = useErrorResponse();

  function onClick(){
    //clear errors before request
    setRequestError();
    //call it with the signature defined on the server
    myMethod({
      num: 1,
      str: "Hello!",
      array: [1, 2, 3],
      etc: null,
    })
      .then(console.log) // prints "Hello RPC!" to the browser's console
      .catch(setRequestError); // catch errors
  }
  return (
   <button onClick={onClick}>Call my method!</button>
   <div>{mainErrorMessage}</div>
  )
}

When throwng ResponseError you can customize the error code, error fields and send custom info to the client.

  if (false) throw new ResponseError({
    message: "Bad request",
    statusCode: 403,

    invalidFields: {
      num: {
        message: "Invalid",
        args: []
      },
      str: {
        message: "Invalid",
        args: []
      }
    },

        payload: {
      some: "custom info",
    },
  });

Using with @mantine/form

import { getAPIMethod } from "@artempoletsky/easyrpc/client";
import { useForm } from "@mantine/form";

import type { FAuthorize } from "../path/to/your/api/route";

// import your zod schemas for Mantine form
import { AAuthorize, authorize as ZAuthorize } from "../path/to/your/api/schemas";

const authorize = getAPIMethod<FAuthorize>("http_path/to/your_route", "authorize");

export default function Page() {
  const form = useForm<AAuthorize>({
    initialValues: {
      userName: "",
      password: "",
    },
    validate: zodResolver(ZAuthorize), // validates on the client
  });

  const [setRequestError, mainErrorMessage] = useErrorResponse(form); // pass a Mantine form

  function onAutorize({ userName, password }: AAuthorize) {
    setRequestError();
    authorize({ userName, password })
      .then(() => {
        // reloading the page
        window.location.href += "";
      })
      .catch(setRequestError);
  }
  
  return (
    <form onSubmit={form.onSubmit(onAutorize)}>
      <TextInput
        {...form.getInputProps("userName")} // the magic will handle all field errors for you
        placeholder="username"
      />
      <TextInput
        {...form.getInputProps("password")} // the magic will handle all field errors for you
        placeholder="password" type="password" />
      <Button type="submit">Login</Button>
      <div>{mainErrorMessage}</div>
    </form>
  )
}
1.2.2

2 months ago

1.2.0

2 months ago

1.2.1

2 months ago

1.1.1

3 months ago

1.1.0

3 months ago

1.1.3

3 months ago

1.1.2

3 months ago

1.0.3

3 months ago

1.0.2

4 months ago

1.0.1

4 months ago

1.0.0

4 months ago