@makay/rpc
v0.4.0
Published
An RPC library for quick development of seamless full-stack applications.
Downloads
2
Maintainers
Readme
🌐 @makay/rpc
An RPC library for quick development of seamless full-stack applications.
Powered by a Vite plugin and inspired by Telefunc, tRPC and other similar libraries.
✨ Features 🔧 Installation and setup 🚀 Usage
📝 Input validation 🚨 Errors 📦 Async server state 👍 Results 📡 Subscriptions
✨ Features:
- 🎉 End-to-end TypeScript
- 🚫 Zero boilerplate
- 📡 Optional server-sent events support for real-time subscriptions
- 🪶 Extremely small client bundle size addition
- 🔗 Directly import and call tailored server functions from client code
- 📄 Colocate server and client files (or don't)
- 📦 Front-end and back-end framework agnostic
- 📦 Validation library agnostic
- 🚫 Low server overhead with no implicit run-time validations
- 🪝 Use the composables/hooks pattern in server code
- 🔌 Includes adapters for popular libraries like Hono and Zod
- 🧰 Includes utilities for async server state and results
🔧 Installation and setup
Install a single package:
npm i @makay/rpc
yarn add @makay/rpc
pnpm add @makay/rpc
bun add @makay/rpc
Everything is included out-of-the-box!
Set up the Vite plugin:
// vite.config.ts import { rpc } from "@makay/rpc/vite" import { defineConfig } from "vite" export default defineConfig({ plugins: [rpc()], })
You can run both
vite
to start a dev server orvite build
to build for production.Set up the RPC server (example using the included Hono adapter):
// src/server.ts import { serve } from "@hono/node-server" import { createRpc } from "@makay/rpc/hono" import { Hono } from "hono" import { cors } from "hono/cors" const app = new Hono() app.use( "/rpc", cors({ origin: "http://localhost:5173", credentials: true }), await createRpc() ) serve(app, (info) => { console.log(`Server is running on http://localhost:${info.port}`) })
You can run the above file with something like
npx tsx src/server.ts
.You can also run
npx tsx watch src/server.ts
to auto-reload during development.Configure your client:
// src/main.ts import { config } from "@makay/rpc/client" config.url = "http://localhost:3000/rpc" config.credentials = "include"
🚀 Usage
Create client and server files and seamlessly import server types and functions from client code with full TypeScript support!
// src/components/Todos.ts
import { createTodo, getTodos, type Todo } from "./Todos.server"
let todos: Todo[] = []
async function main() {
todos = await getTodos()
console.log(todos)
const newTodo = await createTodo("New Todo")
console.log(newTodo)
}
main()
// src/components/Todos.server.ts
export type Todo = {
id: string
text: string
}
const todos: Todo[] = []
export async function getTodos() {
return todos
}
export async function createTodo(text: string) {
const todo = {
id: crypto.randomUUID(),
text,
}
todos.push(todo)
return todo
}
Serve the above src/components/Todos.ts
through Vite and you should see the array of todos printed to your browser console. Reload the page a bunch of times and you should see the array grow since the state is persisted in the server!
In a real scenario you would store your data in a database rather than in the server memory, of course. The snippets above are merely illustrative.
📝 Input validation
There is no implicit run-time validation of inputs in the server. In the example above, the function createTodo
expects a single string argument. However, if your server is exposed publicly, bad actors or misconfigured clients might send something unexpected which can cause undefined behavior in you program.
Therefore, it is extremely recommended that you validate all function inputs. You can use any validation library you want for this.
Here's a basic example using Zod:
// src/components/Todos.server.ts
import { z } from "zod"
const TextSchema = z.string().min(1).max(256)
export async function createTodo(text: string) {
TextSchema.parse(text)
// `text` is now safe to use since Zod would have
// thrown an error if it was invalid
}
When using the Hono adapter, for instance, the code above will result in a 500 Internal Server Error
when you send an invalid input. In order to return the expected 400 Bad Request
instead, you have many options depending on the libraries, frameworks, and adapters you are using. Here are a few examples:
Catch the Zod error and throw a
ValidationError
from@makay/rpc/server
instead:import { ValidationError } from "@makay/rpc/server" const TextSchema = z.string().min(1).max(256) export async function createTodo(text: string) { try { TextSchema.parse(text) } catch (error) { if (error instanceof ZodError) { throw new ValidationError(error.error.format()) } throw error } // `text` is now safe to use }
This is of course a bit too verbose to be practical.
Use the included Zod adapter:
import { z, zv } from "@makay/rpc/zod" const TextSchema = z.string().min(1).max(256) export async function createTodo(text: string) { zv(text, TextSchema) // `text` is now safe to use }
This is much less verbose than the previous option.
Use the
onError
callback of the Hono adapter:import { serve } from "@hono/node-server" import { createRpc } from "@makay/rpc/hono" import { Hono } from "hono" import { cors } from "hono/cors" import { ZodError } from "zod" const app = new Hono() const rpc = await createRpc({ onError(ctx, error) { if (error instanceof ZodError) { return ctx.json(error.error.format(), 400) } throw error }, }) app.use( "/rpc", cors({ origin: "http://localhost:5173", credentials: true }), rpc ) serve(app, (info) => { console.log(`Server is running on http://localhost:${info.port}`) })
This allows you to catch any unhandled Zod errors and return
400 Bad Request
regardless of which server function threw the error.
You can easily adapt any of the examples above to work with any libraries and frameworks you are using. Remember that @makay/rpc
is completely agnostic.
🚨 Errors
WIP
📦 Async server state
WIP
👍 Results
WIP
📡 Subscriptions
WIP
🔌 Adapters
🔥 Hono
WIP
💎 Zod
WIP
🧑🏻💻 Contributing
Contributions, issues, suggestions, ideas and discussions are all welcome!
This is an extremely young library and a lot can still change, be added and removed.