GET and POST with Next.js SWR and Supabase Auth

Simple workflow for fetching and storing data from the Supabase database using the SWR fetching library for authenticated users.

File: ./pages/tasks.jsconst Tasks = () => {
const { fetcher, mutate } = useSWRConfig()
const { data: tasks } = useSWR('/api/tasks')
const [add, setAdd] = useState({})
...
const handleAdd = async () => {
await fetcher(url, add)
mutate('/api/tasks')
}
...
}
File: ./pages/api/tasks.jsconst Tasks = async (req, res) => {
const token = req.headers.token
const jwt = jwt_decode(token)
supabase.auth.setAuth(token)
if (req.method === 'GET') {
return getTasks(res)
} else if (req.method === 'POST') {
return saveTask(res, req.body, jwt)
}
}

Custom App

Next.js uses the App component to initialize pages. This is where you add the user session management and SWR configuration.

File: ./pages/_app.jsimport { useState, useEffect } from 'react'
import { useRouter } from 'next/router'
import { SWRConfig } from 'swr'
import { supabase } from '../lib/supabase'
import Auth from '../components/Auth'
import '../styles/globals.css'
const MyApp = ({ Component, pageProps }) => {
const router = useRouter()
const [session, setSession] = useState(null)
useEffect(() => {
if (!router.asPath.includes('#access_token')) {
sessionStorage.removeItem('AUTH')
}
setSession(supabase.auth.session())
const { data: authListener } = supabase.auth.onAuthStateChange(
(_event, session) => { setSession(session) }
)
const handleRouteChange = () => setError(false) router.events.on('routeChangeStart', handleRouteChange) return () => {
authListener.unsubscribe()
router.events.off('routeChangeStart')
}
}, [router])
const fetcher = (url, data) => {
const options = {
headers: new Headers({
'Content-Type': 'application/json',
token: session.access_token,
}),
credentials: 'same-origin',
}
if (data) {
options.method = 'POST'
options.body = JSON.stringify(data)
}
return fetch(url, options).then((res) => {
if (!res.ok) {
// global error handling
}
return res.json()
})
}
return session ? (
<SWRConfig value={{ fetcher }}>
<Component {...pageProps} />
</SWRConfig>
) : (
<Auth />
)
}
export default MyApp

Authentication and Session Management

return session ? (
<SWRConfig value={{ fetcher }}>
<Component {...pageProps} />
</SWRConfig>
) : (
<Auth />
)

SWR Custom Fetcher

In this example, I created a custom fetcher for SWR. On every request, I add the Supabase access_tokento the header.

const options = {
headers: new Headers({
'Content-Type': 'application/json',
token: session.access_token,
}),
credentials: 'same-origin',
}
if (data) {
options.method = 'POST'
options.body = JSON.stringify(data)
}
const Tasks = () => {
const { fetcher, mutate } = useSWRConfig()
const { data: tasks } = useSWR('/api/tasks')
const [add, setAdd] = useState({})
...
const handleAdd = async () => {
await fetcher(url, add)
mutate('/api/tasks')
}
...
}

Task Page

Here is a full example of the task page:

File: ./pages/tasks.jsimport { useState } from 'react'
import useSWR, { useSWRConfig } from 'swr'
import Layout from '../components/Layout'
import Tasks from '../components/Tasks'
import AddTask from '../components/AddTask'
const url = '/api/tasks'const Tasks = () => {
const { fetcher, mutate } = useSWRConfig()
const { data: tasks } = useSWR(url)
const [add, setAdd] = useState({
name: '',
status: 'open',
})
const handleAdd = async () => {
await fetcher(url, add)
mutate(url)
}
return (
<Layout>
<Tasks tasks={tasks} />
<AddTask
add={add}
setAdd={setAdd}
handleAdd={handleAdd} />
</Layout>
)
}
export default Tasks

Database Calls via API

I prefer to make the database calls from the server. As the access_tokenis added to every request, it is easy to perform authenticated requests via the Supabase REST API.

File: ./pages/api/tasks.jsimport { supabase } from '../lib/supabase'
import jwt_decode from 'jwt-decode'
const Tasks = async (req, res) => {
const token = req.headers.token
const jwt = jwt_decode(token)
supabase.auth.setAuth(token)
if (req.method === 'GET') {
return getTasks(res)
} else if (req.method === 'POST') {
return saveTask(res, req.body, jwt)
}
return res.status(404).json({ error: 'API method not found' })
}
const getTasks = async (res) => {
const { data, error } = await supabase.from('tasks').select('*')
if (error) return res.status(500).json({ error: error.message })
return res.status(200).json(data)
}
const saveTask = async (res, body, jwt) => {
const input = { ...body, created_by: jwt.sub }
const { data, error } = await supabase
.from('tasks')
.upsert([input])
if (error) return res.status(500).json({ error: error.message })
return res.status(200).json(data)
}
export default Tasks

Conclusion

You can create a slim, secure, and high-performance web application using this application setup. I love it!

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store