GET and POST with Next.js SWR and Supabase Auth

Next.js and Supabase are fantastic technologies. With Supabase, you get a real database in the cloud. But best of all, it comes with authentication, managing user data, row-level security, database functions, and storage built-in. With Supabase you can combine all those tools, which reduces complexity significantly.

This article outlines a simplified application setup using Next.js, SWR data fetching, and Supabase. The app requires that every user is logged in. Then you can make authenticated GET and POST calls on your pages very easily.

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')
}
...
}

On the API level you can then make your authenticated calls to Supabase:

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 />
)

If the user has no valid session, the <Auth />component with the options for log-in will be displayed.

Find more information on how to set up Supabase Auth with Next.js here.

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',
}

In addition, the custom fetcher function accepts a data object. When you add a data object, I assume you want to make a POST request to the server.

if (data) {
options.method = 'POST'
options.body = JSON.stringify(data)
}

In the app, I have access to the fetcher and can simply make the following calls:

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!

--

--

--

I ❤️ Shopify & Next.js. checkout-agency.com, Founder

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

How to Setup your own Medium custom domain

JavaScript Arrow Function Magic

Day 14 — value: reference or copy

React Native : Pinch To Zoom Image Viewer

React Hook

Event Listeners in JavaScript

Page before event.

Escaping Pipeline Hell

Git Patches: The Alternative Solution

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
Markus Tripp

Markus Tripp

I ❤️ Shopify & Next.js. checkout-agency.com, Founder

More from Medium

React / Contentful — Create Entries with Linked Objects

React / Contentful — Create Entries with Linked Objects

Handling Sitemap on NextJS

All side optimized Next.js translations

5 Most Commonly error face is Next.js?

5 Most Commonly error face is Next.js By Rajdeep Singh