28 | React Postgres Components 29 |
30 | 31 |
33 | An experiment on deploying remote functions that run inside{" "}
34 | Postgres using v8, run React SSR, and are easily defined in a{" "}
35 | rpc/
directory:
36 |
39 |53 | 54 |40 |
43 |41 | rpc/hello-world.tsx 42 | { 47 | const [{version}] = sql\`SELECT version()\`; // no \`await\` needed! 48 | return
Hello from inside Postgres: {version}
; 49 | }`), 50 | }} 51 | /> 52 |
55 | And then using them in frontend SSR like this: 56 |
57 | 58 |59 |75 | 76 |60 |
63 |61 | app/page.tsx 62 | 69 |
70 | ; 71 | }`), 72 | }} 73 | /> 74 |
80 |
90 | {list.map((row: any) => {
91 | return (
92 |
93 |
99 | \{row.name}
100 |
101 | );
102 | })}
103 |
104 | );
105 | }
106 | `),
107 | }}
108 | />
109 |
110 | }
111 | />
112 |
113 |
114 |
115 | How does it work?
116 |
117 |
118 |
119 |
120 | Using esbuild and{" "}
121 |
122 | PLV8
123 | {" "}
124 | (a Postgres extension that embeds{" "}
125 |
126 | V8
127 |
128 | ), the functions in the{" "}
129 | rpc/
folder are
130 | bundled and inserted into Postgres as part of the{" "}
131 |
136 | Vercel
137 | {" "}
138 | deployment process.
139 |
140 |
141 |
142 | While experimental, this example is a good illustration of{" "}
143 |
148 | Framework-defined Infrastructure
149 |
150 | . In local dev, the functions are executed in the Node.js runtime and
151 | exist in a unified codebase. Upon{" "}
152 | git push
,
153 | specialized infrastructure (in this case PLV8 functions) is created.
154 |
155 |
156 |
157 | The function source is extended with a minimalist yet useful runtime:
158 |
159 |
160 |
161 | -
162 | - A{" "}
163 |
sql
template tag
164 | literal that wraps{" "}
165 | plv8.execute
166 |
167 | -
168 | - A{" "}
169 |
TextEncoder
{" "}
170 | polyfill fittingly named{" "}
171 |
172 |
177 | fastestsmallesttextencoderdecoder
178 |
179 |
180 | , required for React 18+ SSR
181 |
186 | [1]
187 |
188 | .
189 |
190 |
191 | -
192 | - A{" "}
193 |
console
polyfill
194 | that buffers logs and returns them as part of the rpc protocol so
195 | that they end up in the app logging context.
196 |
197 |
198 |
199 |
200 | This resulting bundle is inserted into Postgres as follows:
201 |
202 |
203 |
204 |
213 |
214 |
215 | Local development
216 |
217 |
218 | While Node.js and PLV8 are both based in V8, a good local dev
219 | experience needed to account for important differences:
220 |
221 |
222 |
223 | -
224 | - Different runtime APIs
225 |
226 |
227 | -
228 | - Sync vs async I/O
229 |
230 |
231 |
232 |
233 | Both of these were solved by leveraging the{" "}
234 |
239 | isolated-vm
240 | {" "}
241 | project transparently during local dev.
242 |
243 |
244 |
245 | For each rpc/
{" "}
246 | function, a V8 Isolate is created without access to Node.js APIs. Our
247 | runtime is loaded on top (like{" "}
248 | sql
and{" "}
249 | TextEncoder
).
250 |
251 |
252 |
253 | To preserve the synchronous{" "}
254 | plv8.execute
API
255 | semantics, we use{" "}
256 |
261 | applySyncPromise
262 | {" "}
263 | which pauses the isolate until the promise that dispatches the query
264 | is resolved outside of it.
265 |
266 |
267 | Production
268 |
269 |
270 | To invoke our functions in production,{" "}
271 | {" "}
{" "}
272 | is issuing a{" "}
273 |
274 | SELECT helloWorld()
275 | {" "}
276 | query to Postgres, which is then streamed to the client via
277 | React Server Components.
278 |
279 |
280 |
281 | This makes it such that the Postgres functions are not exposed
282 | automatically, and gives us more control and integration with the
283 | frontend server side rendering lifecycle.
284 |
285 |
286 |
287 |
288 | FAQ
289 |
290 |
291 |
292 |
293 |
294 | This is an experimental project that is not intended for production
295 | use. However, it is a good illustration of the possibilities of
296 | Framework-defined Infrastructure.
297 |
298 |
299 | Deploying functions to Postgres is a great way to reduce latency and
300 | improve performance by moving computation even closer to
301 | the data. This is especially true for functions that are called
302 | often and return small payloads.
303 |
304 |
305 | Unlike regular Serverless Functions, most Postgres providers
306 | don't offer the necessary automatic scaling and isolation
307 | guarantees that would make this technique suitable for general
308 | purpose use.
309 |
310 |
311 |
312 |
313 |
314 | After{" "}
315 |
320 | Oracle announced
321 | {" "}
322 | JavaScript support in MySQL, lots of{" "}
323 |
328 | high quality
329 | {" "}
330 |
335 | memes
336 | {" "}
337 |
342 | hit
343 | {" "}
344 | the scene:
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 | Given Postgres already had a V8 extension (and prior art like{" "}
353 |
358 | plv8ify
359 |
360 | ), I wanted to see how far I could take this. Turns out: pretty far.
361 |
362 |
363 |
364 |
365 |
366 | The technique is not exclusive to React. The runtime it implements
367 | on top of PLV8 is very minimal.
368 |
369 |
370 |
371 | When the function file extension doesn't end in{" "}
372 | .tsx
or
373 | .jsx
, the React
374 | runtime could be excluded from the bundle as an optimization.
375 |
376 |
377 |
378 |
379 |
380 | Per the{" "}
381 |
382 | inspiration
383 | {" "}
384 | behind this being MySQL's JavaScript support, the runtime and
385 | technique should be trivially portable.
386 |
387 |
388 |
389 |
390 |
391 | While not tested or audited, the security model is similar to
392 | regular Serverless Functions in that the code is executed in an
393 | isolated environment.
394 |
395 |
396 |
397 | These functions, however, are never exposed directly as API Routes
398 | or HTTP endpoints. Instead, in the examples above, they are
399 | themselves invoked from React Server Components like normal SQL.
400 |
401 |
402 |
403 |
404 |
405 | Getting it
406 |
407 |
408 |
409 |
410 | The source code is available on{" "}
411 |
416 |
417 | rauchg/react-postgres-components
418 | {" "}
419 | and released under the MIT license. Elephant icon by{" "}
420 |
425 | Lima Studio
426 |
427 | .
428 |
429 |
430 |
431 | To deploy it, you'll need a{" "}
432 |
433 | Vercel Postgres
434 | {" "}
435 | or{" "}
436 |
437 | Neon
438 | {" "}
439 | database linked to the project.
440 |
441 |