Today we're publishing a release candidate for supabase-js v2, which focuses on “quality-of-life” improvements for developers.
Try it out by running npm i @supabase/supabase-js@rc
Nearly 2 years ago we released supabase-js v1. Since then it has been used in over 17K repositories and has grown to 50K weekly downloads. Supabase users give a lot of great feedback and we've learned some of the largest pain-points as a result.
Major Updates
This release focuses on solving these pain-points. At the same time we want to make the upgrade path for supabase-js as easy as possible, so we've been strategic about the changes we're making. We plan to follow this model going forward: incremental changes over huge rewrites.
Type support
If you followed yesterday's announcement, you will have noticed that we added type generators to the CLI.
supabase start
supabase gen types typescript --local > DatabaseDefinitions.ts
These types can now be used to enrich your development experience:
import type { Database } from './DatabaseDefinitions'
const supabase = createClient<Database>(SUPABASE_URL, ANON_KEY)
const { data } = await supabase.from('messages').select().match({ id: 1 })
ℹ️ Differences from v1
v1 also supported types, but the types were generated from the API rather than the database, so it lost a lot of detailed information. The library also required you to specify the definition in every method call, rather than at the client level.
supabase.from<Database['Message']>('messages').select('*')
New Auth methods
We're removing the signIn()
method in favor of more explicit method signatures:
signInWithPassword()
, and signInWithOtp()
.
// v2
const { data } = await supabase.auth.signInWithPassword({
email: 'hello@example',
password: 'pass',
})
// v1
const { data } = await supabase.auth.signIn({
email: 'hello@example',
password: 'pass',
})
This helps with type hinting. Previously it was difficult for developers to know what they were missing. A lot of developers didn't even realize they could use passwordless magic links.
Data methods return minimal by default
The insert()
, update()
, and upsert()
methods now require you to explicitly append select()
if you want the data to be returned.
// v2
const { data } = await supabase.from('messages').insert({ id: 1, message: 'Hello world' }).select() // select is now explicitly required
// v1
const { data } = await supabase.from('messages').insert({ id: 1, message: 'Hello world' }) // insert would also "select()"
This was another common question in our GitHub Discussions. While the idea of automatically returning data is great,
developers often turn on Row Level Security (which is great), and then they forget to add a select
Policy.
It is a bit surprising that you need to add a select
policy to do an insert
, so we opted for the “principle of least surprise”.
If you don't append select()
, the data
value will be an empty object: {}
.
ℹ️ Differences from v1
Previously you could pass a returning: 'minimal'
option to the insert()
, update()
, and
upsert()
statements. We've now made this the default behaviour.
Auth Admin methods
We've move all server-side Auth methods from supabase.auth.api
to supabase.auth.admin
:
// v2
const { data: user, error } = await supabase.auth.admin.listUsers()
// v1
const { data: user, error } = await supabase.auth.api.listUsers()
All admin
methods expect a SERVICE_ROLE
key.
This change makes it clear that any methods under the admin
namespace should only be used on a trusted server-side environment.
Async Auth overhaul
We've rebuilt the Auth library, making it async for almost all methods.
// v2
const { data } = await supabase.auth.getSession()
// v1
const { data } = supabase.auth.session()
This solves the “getting logged out” issue, which has been a recurring challenge in our GitHub Discussions.
ℹ️ Improvements from v1
The previous implementation had a race condition when refreshing the auth token across multiple tabs. The async re-write forces the library to wait for a valid/invalid session before taking any action.
Scoped constructor config
We're being much more explicit about the modular approach that supabase-js
uses:
const supabase = createClient(apiURL, apiKey, {
db: {
schema: 'public',
},
auth: {
autoRefreshToken: true,
persistSession: true,
},
realtime: {
channels,
endpoint,
},
// common across all libraries
global: {
fetch: customFetch,
headers: DEFAULT_HEADERS,
},
})
This is clearer for developers - if you're only using some parts of Supabase, you only recieve the hints for those parts.
ℹ️ Improvements from v1
We noticed a lot of confusion for the variable naming between each library.
For example, Auth has a config parameter called "storageKey", which was was often confused with the storage-js
library bundled in the supabase-js
library.
Better Errors
We've created error types for all of the sub-libraries in supabase-js
. Here's a example for Edge Functions:
import { FunctionsHttpError, FunctionsRelayError, FunctionsFetchError } from '@supabase/supabase-js'
const { data: user, error } = await supabase.functions.invoke('hello')
if (error instanceof FunctionsHttpError) {
console.log('Function returned an error', error.message)
} else if (error instanceof FunctionsRelayError) {
console.log('Relay error:', error.message)
} else if (error instanceof FunctionsFetchError) {
console.log('Fetch error:', error.message)
}
Improvements for Edge Functions
supabase-js
now automatically detects the content type for the request/response bodies for all Edge Function invocations:
// v2
const { data: user, error } = await supabase.functions.invoke('hello', {
body: { foo: 'bar' },
})
// v1
const { data: user, error } = await supabase.functions.invoke('hello', {
headers: { 'Content-Type': 'application/json' }
body: JSON.stringify({ foo: 'bar' }),
})
This improvement inspired by a Supabase community member. Thanks @vejja!
Multiplayer Sneak Peek
There is a new channel()
interface which are releasing in v2.
This is a "preparatory" release for our upcoming multiplayer features.
supabaseClient
.channel('any_string_you_want')
.on('presence', { event: 'track' }, (payload) => {
console.log(payload)
})
.subscribe()
As part of this change, the old from().on().subscribe()
method for listening to database changes will be changing:
// v2
supabaseClient
.channel('any_string_you_want')
.on(
'postgres_changes',
{
event: 'INSERT',
schema: 'public',
table: 'movies',
},
(payload) => {
console.log(payload)
}
)
.subscribe()
// v1
supabase
.from('movies')
.on('INSERT', (payload) => {
console.log(payload)
})
.subscribe()
You can listen to PostgreSQL database changes on any channel you want by subscribing to the 'postgres_changes'
event.
For now, we will continue to support from().on().subscribe()
, but in the future we will deprecate this in favor of the channel().on().subscribe()
method.
Community
Version 2.0 is the result of the combined work of over 100 contributors to our libraries, and over 450 contributors to our docs and websites. If you're one of those contributors, thank you.
functions-js
(4)gotrue-js
(47)postgrest-js
(30)realtime-js
(16)storage-js
(17)supabase-js
(39)
Special shout outs to: @vejja, @pixtron, @bnjmnt4n, and @karlseguin.
Migrating to v2
Update today by running:
npm i @supabase/supabase-js@rc
You can find a full list of updates, breaking changes, and a migration guide in our Release Notes.
We'll continuing merging security fixes to v1, with maintenance patches for the next three months.
Announcement video and discussion
Links
-
Release Notes & Migration Guide
More Launch Week 5