โ Aleksandra Sikora, @aleksandrasays
- full-stack engineer,
- rock climber,
- organizer of
ย Wrocลaw TypeScript
previously
- Blitz.js Maintainer
- Hasura Console Tech Lead
๐ฆ @aleksandrasays
๐ @beerose
๐ @aleksandra@mas.to
๐ https://aleksandra.codes
๐ฅด
Things get complicated
๐ญ
Boilerplate
Lost typesafety
Repetitive error handling
= additional complexity
๐ค
REST
SOAP
GraphQL
๐ค
Fullstack TypeScript app
TypeScript
TypeScript
tRPC: query & mutation procedures
Remix: loader pattern
Blitz RPC: query & mutation resolvers
React: Server-side components
tRPC: query & mutation procedures
Remix: loader pattern
Blitz RPC: query & mutation resolvers
React: Server-side components
โ ๏ธ it doesn't mean NO API layer
tRPC: query & mutation procedures
Remix: loader pattern
Blitz RPC: query & mutation resolvers
React: Server-side components
To show you alternative solutions to REST and GraphQL that can let you iterate faster and make fullstack web development easier.
You can take a rest from REST, but you don't have to...
You can take a rest from REST, but you don't have to...
You can take a rest from REST, but you don't have to...
// one-computer.js
function welcome(name) {
return `Hello, ${name}!`
}
const greeting = welcome("JSConf Chile")
// ^ "Hello, JSConf Chile!"
// server.js
function welcome(name) {
return `Hello, ${name}!`
}
startImaginaryServer({ welcome })
// client.js
const greeting = await fetch(
`https://aleksandra.says/rpc/welcome`,
{ body: JSON.stringify("JSConf Chile") }
)
RPC is the practice of remotely calling functions
๐
๐ฅด
NOT รORBA
AND NOT COBRA
module Finance {
typedef sequence<string> StringSeq;
struct AccountDetails {
string name;
StringSeq address;
long account_number;
double current_balance;
};
exception insufficientFunds { };
interface Account {
void deposit(in double amount);
void withdraw(in double amount)
raises(insufficientFunds);
readonly attribute AccountDetails details;
};
};
<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<soap:Header>
</soap:Header>
<soap:Body>
<m:GetUser>
<m:UserId>123</m:UserId>
</m:GetUser>
</soap:Body>
</soap:Envelope>
<?xml version="1.0"?>
<soap:Envelope
xmlns:soap="http://www.w3.org/2003/05/soap-envelope/"
soap:encodingStyle="http://www.w3.org/2003/05/soap-encoding">
<soap:Body>
<m:GetUserResponse>
<m:Username>Tony Stark</m:Username>
</m:GetUserResponse>
</soap:Body>
</soap:Envelope>
<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<soap:Header>
</soap:Header>
<soap:Body>
<m:GetUser>
<m:UserId>123</m:UserId>
</m:GetUser>
</soap:Body>
</soap:Envelope>
When the web started changing
Set of architectural constraints
Intended to be consumed by multiple clients
Can request and update resources
Exposes resources
Set of architectural constraints
Intended to be consumed by multiple clients
Operation | RPC | REST |
---|---|---|
Login | POST /login | POST /sessions |
Logout | POST /logout | DELETE /sessions |
Get user by id | GET /getUser?id=123 | GET /users/123 |
Get user's todo items | GET /getTodos?userId=123 | GET /users/123/todos |
Add new todo item | POST /addTodo | POST users/123/todos |
Update todo item | POST /updateTodo | PUT /todos/1ย |
Delete todo item | POST /deteteTodo | DELETE /todos/1 |
RPC over HTTP using JSON
RESTful
"REST"
API
App
GET users/
GET tasks/
GET tags/
API
App
POST graphql/
Body:
{ "query": "query { users {...} }" }
User 1
Task 1
Task 2
Tag 1
Tag 2
query {
user(id: 1) {
name
tasks {
name
status
tags {
id
}
}
}
}
name
surname
age
status
name
priority
name
priority
status
description
id
Tag 3
id
description
id
description
id
at least until stuff like Max Stoiber's GraphQL CDN popped up
Revisiting the original promise of RPC
As TypeScript and static typing increasingly becomes a best practice in web development, API contracts present a major pain point. We need better ways to statically typeย our API endpoints and share those typesย between our client and server (or server-to-server).
โ tRPC docs
// server.ts
import db from "prisma"
async function welcome(userId: string) {
const user = await db.users.findFirst({ where: { id: userId }})
return `Welcome again, ${user.name}!`
}
startImaginaryServer({ welcome })
// client.ts
const greeting = await fetch(
`https://aleksandra.says/rpc/welcome`,
{ body: JSON.stringify(1) }
)
Type safe backend
Unsafe frontend
// server.js
function welcome(name) {
return `Hello, ${name}!`
}
startImaginaryServer({ welcome })
โ
Type safe backend
Unsafe frontend
// server.ts
import db from "prisma"
async function welcome(userId: string) {
const user = await db.users.findFirst({ where: { id: userId }})
return `Welcome again, ${user.name}!`
}
startImaginaryServer({ welcome })
// client.ts
const greeting = await fetch(
`https://aleksandra.says/rpc/welcome`,
{ body: JSON.stringify(1) }
)
// client.ts
import type { Server } from './server.ts'
const rpc = setupImaginaryClient<Server>()
const greeting = await rpc.welcome({ input: 1 })
// server.ts
import db from "prisma"
async function welcome(userId: string) {
const user = await db.users.findFirst({ where: { id: userId }})
return `Welcome again, ${user.name}!`
}
const server = startImaginaryServer({ welcome })
export type Server = typeof server
Evolution & Iteration
We keep revisiting ideas from the past and improving on them.
Sometimes what you need today isn't the new, but a spin off of something old
Follow your needs
Keep in mind the needs of your software. Newer doesn't mean better. Pay attention to the hype to know what's up; don't toss away solutions that work for you.
REST is not dead, but it's not your only choice
It's always worth knowing all your options. RPC in a JavaScript world can be a good fit!
RPC is a natural fit for the increasingly ubiquitous practice of full-stack development with frameworks such as Next.js.
โ Telefunc docs
@aleksandrasays
www.aleksandra.codes
@aleksandra@mas.to