Take a Rest from REST
โ Aleksandra Sikora, @aleksandrasays
whoami
- 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
What is an API?
Why are we talking about APIs?
๐ฅด
Server & Client
Things get complicated
๐ญ
API Layer Problems
Boilerplate
Lost typesafety
Repetitive error handling
= additional complexity
๐ค
API Layer Solutions
REST
SOAP
GraphQL
๐ค
API Layer Problems
Fullstack TypeScript app
TypeScript
TypeScript
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
โ ๏ธ it doesn't mean NO API layer
API Layer Problems
tRPC: query & mutation procedures
Remix: loader pattern
Blitz RPC: query & mutation resolvers
React: Server-side components
What is the goal of this talk?
To show you alternative solutions to REST and GraphQL that can let you iterate faster and make fullstack web development easier.
What is the goal of this talk?
You can take a rest from REST, but you don't have to...
What is the goal of this talk?
You can take a rest from REST, but you don't have to...
New solutions donโt mean that the previous ones are bad.
You can take a rest from REST, but you don't have to...
RPC
1981
What is RPC?
// one-computer.js
function welcome(name) {
return `Hello, ${name}!`
}
const greeting = welcome("JSConf Chile")
// ^ "Hello, JSConf Chile!"
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("JSConf Chile") }
)
RPC is the practice of remotely calling functions
๐
๐ฅด
- Client & server tightly coupled
- Having to use the same language
- Need to learn all the procedure names
- Having to use multi-threaded servers
- Parameters marshalling
- Exception handling
Problems with RPC
What's next ๐
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
What's next ๐
SOAP
1998
<?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>
- Heavy, requires more bandwidth
- POST = no cache on HTTP layer
- Tightly coupled with server
- Inflexible
Problems with SOAP
What's next ๐
REST
2000
When the web started changing
-
Set of architectural constraints
-
Intended to be consumed by multiple clients
REST APIs
Can request and update resources
Exposes resources
REST APIs
-
Set of architectural constraints
-
Intended to be consumed by multiple clients
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"
- Over fetching
- Big payloads
- n+1 problem
- Limiting constraints
- No end-to-end typesafety
Problems with REST
What's next ๐
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
- 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
What's next ๐
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
Let's see some code!
https://blitzjs.com
https://trpc.io/
ย
- Client & server tightly coupled
- Having to use the same language
- Need to learn all the procedure names
- Having to use multi-threaded servers
- Parameters marshalling
- Exception handling
Problems (?) with RPC
Takeaways
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
1
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.
2
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!
3
ใ
RPC is a natural fit for the increasingly ubiquitous practice of full-stack development with frameworks such as Next.js.
โ Telefunc docs
ยกMuchas gracias!
@aleksandrasays
www.aleksandra.codes
@aleksandra@mas.to
JSConf Chile: Take a Rest From REST
By Aleksandra Sikora
JSConf Chile: Take a Rest From REST
- 1,379