Take a Rest from REST
β Aleksandra Sikora, @aleksandrasays
What's this talk about?

What is an API?

Why are we talking about APIs?
π©βπ»
API Layer Problems
π
API Layer Problems
Boilerplate
Lost typesafety
Repetitive error handling
= additional complexity
π€
API Layer Problems
Boilerplate
Lost typesafety
Repetitive error handling
API Layer Problems
πββοΈ
tRPC: query & mutation procedures
Remix: loader pattern
Blitz RPC: query & mutation resolvers
React: Server-side components
API Layer Problems
πββοΈ
tRPC: query & mutation procedures
Remix: loader pattern
Blitz RPC: query & mutation resolvers
React: Server-side components
RPC
1981

What is RPC?
// one-computer.js
function welcome(name) {
return `Hello, ${name}!`
}
const greeting = welcome("React Berlin")
// ^ "Hello, React Berlin!"
What is RPC?
// server.js
function welcome(name) {
return `Hello, ${name}!`
}
startImaginaryServer({ welcome })
// client.js
const greeting = await fetch(
`https://aleksandra.says/rpc/welcome`,
{ body: JSON.stringify("React Berlin") }
)
What is RPC?
// server.js
function welcome(name) {
return `Hello, ${name}!`
}
startImaginaryServer({ welcome })
// client.js
const greeting = await fetch(
`https://aleksandra.says/rpc/welcome`,
{ body: JSON.stringify("React Berlin") }
)
RPC is the practice of remotely calling functions
π
π₯΄
- No request cancellation
- Having to use multi-threaded servers
- Parameters marshalling
- Exception handling
Problems with RPC
Solution π
CORBA
1991
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;
};
};
IDL
- Complexity
- Steep learning curve
- Mapping problems
- Name confused with a poisonous snake
Problems with CORBA
Solution π
SOAP
1998
<?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>
- Heavy, requires more bandwidth
- POST = no cache on HTTP layer
- Tightly coupled with server
Problems with SOAP
Solution π
REST
2000
Can request and update resources
Exposes resources
- Uniform interface
- Clientβserver architecture
- Stateless
- Cacheable
- Layered system
- Code on demand (optional)
REST APIs


SOAP vs. REST
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 vs. REST

RPC over HTTP using JSON
RESTful
"REST"
- Under and over fetching
- Big payloads
- n+1 problem
- Limiting constraints
- No end-to-end typesafety
Problems with REST
Solution π
GraphQL
2012
REST API
GraphQL API
API
App
GET users/
GET tasks/
GET tags/
API
App
POST graphql/
Body:
{ "query": "query { users {...} }" }
vs
Client controls the data it gets
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

- Same POST-caching problem as in SOAP
- You have to generate types
- If you use tools like Hasura,
you push a lot of domain logic to frontend - Otherwise β boilerplate!
Problems with GraphQL
at least until stuff like Max Stoiber's GraphQL CDN popped up
What's nextβ
- Same POST-caching problem as in SOAP
- You have to generate types
- If you use tools like Hasura,
you push a lot of domain logic to frontend - Otherwise β boilerplate!
Problems with GraphQL

RPC
1981 2022
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

DEMO
Takeaway
Cycles in technology
- We keep revisiting ideas from decades past and improving on them
- Sometimes what you need today isn't the new, but a spin-off of something old
γ
RPC is a natural fit for the increasingly ubiquitous practice of full-stack development with frameworks such as Next.js.
β Telefunc docs
Thank You!

@aleksandrasays
www.aleksandra.codes
www.everybody.gives
Serif
By Aleksandra Sikora
Serif
- 1,631