proz
proz is a functional RPC-based API Layer for your Client-Server-Apps, which
allows you to call your fully typed API methods directly inside your client.
tl;dr: just scroll down for a complete example.
This Library is currently a work in progress and not ready for production.
Summary
proz empowers you to move parts of your business logic from your client to your
server, so your client can take care of the UI, and your server can do the data
crunching.
- proz makes heavy use of TypeScript's type inference, reducing the amount of
types you need to manually define and maintain. It's fully typed – even if you
don't define any types at all.
- proz does not specify nor recommend how your API should be structured. Because
it's just a (maybe huge) collection of functions, which you created for your
own needs. You don't need to build a REST- or GraphQL-API for your own
Backend-for-Frontend, just a convenient and simple RPC-like interface.
- Supports async middleware.
- Supports schema validation of your request params and body.
- Includes an autogenerated API client, without any compilation or
babel-plugins. It's fully powered by TypeScript.
- proz' API client is a tiny 250 Bytes (150 Bytes gzipped).
Info: This package is native ESM.
Details
proz consists of a Resolver and Client.
The Resolver is the server part, where you define your API methods as
Queries and Mutations. The Resolver takes a request, resolves which API method
should be executed, executes it (with its middleware), and returns the result.
The Client is an easy-to-use fully typed and tiny Client, which calls the
Resolver. The Client doesn't need any autogeneration or babel plugin. It's a
JavaScript Proxy , which knows about all the type definitions of your Resolver.
So it knows which methods are defined, what parameters they expect, and what
data they return. Because it's only powered by types, it's a measly 250 Bytes
(150 Bytes gzipped).
Example
import { proz } from 'proz'
const todosCtx = proz.pipe(
async (ctx) => {
const user = await db.user.findById(ctx.req.cookies['user_id'])
return { ...ctx, user }
},
proz.params(yup.object({
status: yup.string().oneOf(['todo', 'done']),
limit: yup.number().max(20)
}))
)
const todos = proz.query(todosCtx, async (ctx) => {
const todos = await db.todo.findMany({
where: { status: ctx.params.status }
limit: ctx.params.limit || 10
})
return todos
})
const openTodos = proz.query(async () => {
const todos = await db.todo.findMany({
where: { status: 'open' }
})
return todos
})
const doneTodos = proz.query(async () => {
const todos = await db.todo.findMany({
where: { status: 'done' }
})
return todos
})
const addTodoCtx = proz.pipe(
authentication,
proz.body(yup.object({ id: yup.string().required() }))
)
const addTodo = proz.mutation(addTodoCtx, async (ctx) => {
const todo = await db.todo.create({
...ctx.body,
userId: ctx.user.id
})
return todo
)
import { createProzResolver } from 'proz'
const resolver = createProzResolver({
todos,
openTodos,
doneTodos,
addTodo
})
export type Resolver = typeof resolver
export default async (req, res) => {
const data = resolver.handleRequest(req, res)
res.json(data)
}
import { createProzClient } from 'proz'
import type { Resolver } from './whatever'
const api = createProzClient<Resolver>({
fetch: ({ proc, method, body, params }) => {
return ky(`/api/${proc}`, {
method,
json: body,
searchParams: params
}).json()
}
})
const todos = await api.query.todos({ status: 'todo' })
const openTodos = await api.query.openTodos()
const doneTodos = await api.query.doneTodos()
async function handleAddClick() {
const newTodo = await api.mutate.addTodo({
name: 'Buy Milk'
})
console.log(newTodo)
}
<button onClick={handleAddClick}>
Add Todo
</button>
Wishlist
What I'm thinking about adding...