Skip to content

Commit 2c067a0

Browse files
blog: HTTP Requests in TypeScript Serverless Functions (#689)
* blog: HTTP Requests in TypeScript Serverless Functions Signed-off-by: David Dal Busco <david.dalbusco@outlook.com> * blog: headers Signed-off-by: David Dal Busco <david.dalbusco@outlook.com> * docs: image Signed-off-by: David Dal Busco <david.dalbusco@outlook.com> --------- Signed-off-by: David Dal Busco <david.dalbusco@outlook.com>
1 parent 31a7fba commit 2c067a0

3 files changed

Lines changed: 162 additions & 0 deletions

File tree

116 KB
Loading
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
---
2+
slug: http-requests-typescript-serverless-functions
3+
title: HTTP Requests in TypeScript Serverless Functions
4+
authors: [peterpeterparker]
5+
tags: [functions, typescript, sputnik, serverless, fetch, api]
6+
date: 2026-04-05
7+
image: https://juno.build/assets/images/http-requests-typescript-serverless-functions-548b19a5c5103a1c7d99e6018a74ecea.png
8+
---
9+
10+
![](http-requests-typescript-serverless-functions.png)
11+
12+
One of the most requested features for TypeScript serverless functions has always been the ability to make HTTP requests to external APIs.
13+
14+
That's now supported 🚀.
15+
16+
---
17+
18+
## HTTP Requests
19+
20+
Rust serverless functions have been able to reach out to the outside world via HTTPS for a while. That comes naturally since Rust functions leverage the `ic_cdk`, which is maintained by the DFINITY foundation for the Internet Computer and therefore, supports it out of the box.
21+
22+
On the other hand, TypeScript serverless functions are unique to Juno, so features are added incrementally.
23+
24+
Performing those kind of requests happens through a feature called HTTPS outcalls. Using it, you can extend your Satellite to fetch data, trigger webhooks, call third-party services, whatever your use case requires. In Juno's own codebase, it is used to send emails and fetch the public keys of the OpenID providers supported for authentication.
25+
26+
---
27+
28+
## Making a Request
29+
30+
Here's a simple example using the [Dog CEO API](https://dog.ceo/dog-api/) to fetch a random dog image URL and return it directly from an update function:
31+
32+
```typescript
33+
import { defineUpdate } from "@junobuild/functions";
34+
import { httpRequest, type HttpRequestArgs } from "@junobuild/functions/ic-cdk";
35+
import { j } from "@junobuild/schema";
36+
37+
const DogSchema = j.strictObject({
38+
message: j.url(),
39+
status: j.string()
40+
});
41+
42+
export const fetchRandomDog = defineUpdate({
43+
result: DogSchema,
44+
handler: async () => {
45+
const args: HttpRequestArgs = {
46+
url: "https://dog.ceo/api/breeds/image/random",
47+
method: "GET",
48+
isReplicated: false
49+
};
50+
51+
const result = await httpRequest(args);
52+
53+
const decoder = new TextDecoder();
54+
const body = decoder.decode(result.body);
55+
56+
return JSON.parse(body);
57+
}
58+
});
59+
```
60+
61+
Define the function, call the API, use the result. That's it.
62+
63+
![Magic GIF for fun](./magical-magic.gif)
64+
65+
---
66+
67+
## Transformer
68+
69+
Some APIs return response headers that vary between nodes, timestamps, request IDs, and so on. In replicated mode, where the request is run multiple times and reconciled to ensure its validity, this can cause the call to fail if all responses do not match.
70+
71+
To handle this, you can define a transform function that sanitizes the response before the nodes compare it. A common pattern is to strip the headers entirely:
72+
73+
```typescript
74+
import { defineQuery, defineUpdate } from "@junobuild/functions";
75+
import {
76+
httpRequest,
77+
HttpRequestResultSchema,
78+
TransformArgsSchema,
79+
type HttpRequestArgs
80+
} from "@junobuild/functions/ic-cdk";
81+
import { j } from "@junobuild/schema";
82+
83+
const DogSchema = j.strictObject({
84+
message: j.url(),
85+
status: j.string()
86+
});
87+
88+
export const fetchRandomDog = defineUpdate({
89+
result: DogSchema,
90+
handler: async () => {
91+
const args: HttpRequestArgs = {
92+
url: "https://dog.ceo/api/breeds/image/random",
93+
method: "GET",
94+
isReplicated: true,
95+
transform: "trimHeaders"
96+
};
97+
98+
const result = await httpRequest(args);
99+
100+
const decoder = new TextDecoder();
101+
const body = decoder.decode(result.body);
102+
103+
return JSON.parse(body);
104+
}
105+
});
106+
107+
export const trimHeaders = defineQuery({
108+
hidden: true,
109+
args: TransformArgsSchema,
110+
result: HttpRequestResultSchema,
111+
handler: ({ response: { status, body } }) => ({
112+
status,
113+
body,
114+
headers: []
115+
})
116+
});
117+
```
118+
119+
The `transform` field references the name of an exported query function. Marking it `hidden: true` keeps it out of the auto-generated client API, it's an implementation detail, not something you'd call from the frontend.
120+
121+
---
122+
123+
## Guards
124+
125+
This release also ships support for guards that let you protect your functions with access control logic, restricting who can call them before the handler even runs.
126+
127+
```typescript
128+
import { defineQuery } from "@junobuild/functions";
129+
import { callerIsAdmin } from "@junobuild/functions/sdk";
130+
131+
export const ping = defineQuery({
132+
guard: () => {
133+
throw new Error("No pong today");
134+
},
135+
handler: () => {
136+
console.log("Hello");
137+
}
138+
});
139+
140+
export const hello = defineQuery({
141+
guard: callerIsAdmin,
142+
handler: () => {
143+
console.log("Hello, admin!");
144+
}
145+
});
146+
```
147+
148+
---
149+
150+
## References
151+
152+
Following sections of the documentation have been updated:
153+
154+
- [Development](/docs/build/functions/development/)
155+
- [Guides](/docs/guides/typescript)
156+
- [References (SDK, schema, etc.)](/docs/reference/functions/typescript)
157+
158+
---
159+
160+
HTTP requests were a feature often requested and led a few developers to choose Rust for that reason. Really happy to finally ship this one and hopefully see more devs embrace the simplicity of using TypeScript.
161+
162+
To infinity and beyond<br />David
1.85 MB
Loading

0 commit comments

Comments
 (0)