1670403203
In this video we show how to create a React application with Supabase that allows users to store images in their own image galleries. Users will log in with Supabase Magic Link authorization, and then we handle image upload with PostgreSQL and Supabase Storage. This project is great for any React developers interested in large file storage, and creating a system like this video is a great step forward when creating apps that can scale with massive traffic. You can apply the concepts in this video to creating social media feeds, creating sharable MP3s, sharing 3D objects across users, and many other use cases involving larger files! End of story: Supabase bucket storage is epic.
0:00 Intro
1:40 Initialize Supabase app
2:38 Setup React app + Supabase initialization
4:50 Creating Magic Link login page
11:57 Creating Image Gallery page
13:45 Setting up Supabase Storage
15:59 Image upload to gallery
21:15 Get images from Supabase Storage
24:30 Explaining CDN links
27:15 Showing images to user
30:37 Delete user image
32:49 Functionality test, thanks for watching!
Full Code: https://github.com/coopercodes/SupabaseImageGallery
1666749882
Udemy Course (with discount): https://bit.ly/3DxBO1S
Timeline:
0:00 - Project 1
1:38:58 - Project 2
2:48:51 - Project 3
In this course we will take you from a Vue 3 novice to a job ready engineer. This course is loaded with practical projects and examples so that you can truly understand and utilize Vue 3 and the composition API in great depth.
We will be building five projects, each one getting more and more complex. We will end this course by building an Instagram clone with features like file upload and user authentication. By the end of this course, you should have multiple practical example to show off your knowledge!
Here are a list of thing you will learn in this course:
I really hope you enjoy this course and learn a ton from it!
#vue #vuejs #pinia #typescript #supabase
1660881900
Khi nói đến việc xây dựng và lựa chọn các khuôn khổ cho ứng dụng full-stack tiếp theo của bạn, kết hợp Next.js với Supabase là một trong những lựa chọn tốt nhất để làm việc theo ý kiến của tôi.
Supabase là một giải pháp thay thế Firebase mã nguồn mở với rất nhiều công cụ mạnh mẽ, bao gồm xác thực liền mạch . Là một nhà phát triển, đây là chìa khóa để xây dựng một ứng dụng full-stack thành công.
Cùng với xác thực, Supabase đi kèm với các tính năng khác, chẳng hạn như cơ sở dữ liệu Postgres, đăng ký thời gian thực và lưu trữ đối tượng. Tôi tin rằng Supabase là một trong những dịch vụ phụ trợ dễ dàng nhất để bắt đầu hoặc tích hợp.
Trong bài viết này, chúng ta sẽ tìm hiểu cách xây dựng một ứng dụng đầy đủ bằng cách sử dụng Next.js và Supabase . Chúng ta sẽ nói về cách thiết lập dự án Supabase, định cấu hình giao diện người dùng, triển khai xác thực và các chức năng.
Khái niệm của ứng dụng này là để người dùng theo dõi và tạo các hoạt động tập luyện dựa trên các thông số được chỉ định, chỉnh sửa các hoạt động này nếu có bất kỳ sai sót hoặc thay đổi cần thiết nào và xóa chúng nếu cần. Bắt đầu nào!
Next.js là một trong những cách dễ nhất và phổ biến nhất để xây dựng các ứng dụng React sẵn sàng sản xuất. Trong những năm gần đây, Next.js đã có sự tăng trưởng đáng kể theo cấp số nhân và nhiều công ty đã áp dụng nó để xây dựng các ứng dụng của họ.
Supabase là một giải pháp thay thế mã nguồn mở, không máy chủ cho Firebase được xây dựng trên cơ sở dữ liệu PostgreSQL. Nó cung cấp tất cả các dịch vụ phụ trợ cần thiết để tạo một ứng dụng full-stack.
Với tư cách là người dùng, bạn có thể quản lý cơ sở dữ liệu của mình từ giao diện Supabase, từ việc tạo bảng và mối quan hệ để viết các truy vấn SQL và công cụ thời gian thực trên PostgreSQL.
Supabase đi kèm với các tính năng thực sự thú vị giúp việc phát triển ứng dụng toàn ngăn xếp của bạn trở nên dễ dàng hơn. Một số tính năng này là:
auth.users
bảng ngay sau khi bạn tạo cơ sở dữ liệu của mình. Khi bạn tạo một ứng dụng, Supabase cũng sẽ chỉ định người dùng và ID ngay khi bạn đăng ký trên ứng dụng có thể được tham chiếu trong cơ sở dữ liệu. Đối với các phương pháp đăng nhập, có nhiều cách khác nhau để bạn có thể xác thực người dùng như email, mật khẩu, liên kết ma thuật, Google, GitHub, v.v.Để bắt đầu dự án của chúng tôi trong terminal với mẫu Next.js, chúng tôi sẽ chạy lệnh sau:
npx create-next-app nextjs-supabase
nextjs-supabase
là tên thư mục của ứng dụng của chúng tôi, nơi chúng tôi sẽ bao gồm mẫu ứng dụng Next.js.
Chúng tôi sẽ cần cài đặt gói ứng dụng Supabase để kết nối với ứng dụng Next.js của chúng tôi sau này. Chúng ta có thể làm như vậy bằng cách chạy một trong các lệnh sau:
yarn add @supabase/supabase-js
hoặc
npm i @supabase/supabase-js
Khi ứng dụng đã hoàn tất thiết lập, hãy mở thư mục trong trình chỉnh sửa mã yêu thích của bạn. Bây giờ, chúng tôi có thể xóa mẫu cơ bản trong /pages/index.js
tệp của mình và thay thế bằng h1
tiêu đề “Chào mừng bạn đến với Ứng dụng Workout”.
Sau khi hoàn tất, hãy chạy lệnh yarn dev
trong terminal để khởi động ứng dụng của bạn tại http: // localhost: 3000 . Bạn sẽ thấy một trang như thế này:
Để thiết lập dự án Supabase, hãy truy cập app.supabase.com để đăng nhập vào trang tổng quan ứng dụng bằng tài khoản GitHub của bạn.
Sau khi đăng nhập, bạn có thể tạo tổ chức của mình và thiết lập một dự án mới trong tổ chức đó bằng cách nhấp vào Tất cả dự án .
Nhấp vào Dự án mới và đặt tên và mật khẩu cơ sở dữ liệu cho dự án của bạn. Nhấp vào nút Tạo dự án mới ; sẽ mất vài phút để dự án của bạn bắt đầu và chạy.
Khi dự án đã được tạo, bạn sẽ thấy một bảng điều khiển như sau:
Đối với hướng dẫn này, tôi đã tạo một dự án có tênworkout-next-supabase.
Bây giờ, hãy tạo bảng cơ sở dữ liệu của chúng ta bằng cách nhấp vào biểu tượng SQL Editor trên trang tổng quan của chúng tôi và nhấp vào Truy vấn mới . Nhập truy vấn SQL bên dưới vào trình soạn thảo và nhấp vào RUN để thực hiện truy vấn.
CREATE TABLE workouts (
id bigint generated by default as identity primary key,
user_id uuid references auth.users not null,
user_email text,
title text,
loads text,
reps text,
inserted_at timestamp with time zone default timezone('utc'::text, now()) not null
);
alter table workouts enable row level security;
create policy "Individuals can create workouts." on workouts for
insert with check (auth.uid() = user_id);
create policy "Individuals can update their own workouts." on workouts for
update using (auth.uid() = user_id);
create policy "Individuals can delete their own workouts." on workouts for
delete using (auth.uid() = user_id);
create policy "Workouts are public." on workouts for
select using (true);
Thao tác này sẽ tạo bảng tập luyện mà chúng tôi sẽ sử dụng để xây dựng ứng dụng CRUD của mình.
Cùng với việc tạo bảng, các quyền ở cấp độ hàng sẽ được bật để đảm bảo rằng chỉ những người dùng được ủy quyền mới có thể tạo, cập nhật hoặc xóa các chi tiết về bài tập của họ.
Để xem bảng tập luyện trông như thế nào, chúng ta có thể nhấp vào biểu tượng Trình chỉnh sửa bảng trên trang tổng quan để xem bảng tập luyện mà chúng ta vừa tạo.
Đối với ứng dụng này, chúng ta sẽ có bảy cột:
user_id
user_email
id
title
loads
reps
Date stamp
Khi bảng và cột của chúng ta đã được thiết lập, bước tiếp theo là kết nối cơ sở dữ liệu Supabase với ứng dụng giao diện người dùng Next.js của chúng ta!
Để kết nối Supabase với ứng dụng Next.js của chúng tôi, chúng tôi sẽ cần URL dự án và Anon Key . Cả hai điều này có thể được tìm thấy trên bảng điều khiển cơ sở dữ liệu của chúng tôi. Để lấy hai phím này, hãy nhấp vào biểu tượng bánh răng để chuyển đến Cài đặt và sau đó nhấp vào API . Bạn sẽ thấy hai phím này hiển thị như sau:
Tất nhiên, chúng tôi không muốn để lộ những giá trị này một cách công khai trên trình duyệt hoặc kho lưu trữ của chúng tôi vì đó là thông tin nhạy cảm. Với lợi thế của chúng tôi, Next.js cung cấp hỗ trợ sẵn có cho các biến môi trường cho phép chúng tôi tạo một .env.local
tệp trong thư mục gốc của dự án của chúng tôi. Điều này sẽ tải các biến môi trường của chúng tôi và hiển thị chúng với trình duyệt bằng cách thêm tiền tố vào NEXT_PUBLIC
.
Bây giờ, hãy tạo một .env.local
tệp trong thư mục gốc của dự án và bao gồm URL và khóa của chúng tôi trong tệp.
.env.local NEXT_PUBLIC_SUPABASE_URL = // dán url dự án của bạn vào đây NEXT_PUBLIC_SUPABASE_ANON_KEY = // dán khóa anon siêu dữ liệu của bạn vào đây
NB, Đừng quên đưa
.env.local
vàogitignore
tệp của bạn để ngăn nó bị đẩy lên repo GitHub (và có sẵn cho mọi người xem) khi triển khai.
Bây giờ chúng ta hãy tạo tệp khách hàng Supabase của chúng ta bằng cách tạo một tệp được gọi supabase.js
ở gốc của dự án của chúng ta. Bên trong supabase.js
tệp, chúng tôi sẽ viết mã sau:
// supabase.js
import { createClient } from "@supabase/supabase-js";
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
export const supabase = createClient(supabaseUrl, supabaseKey);
Ở đây, chúng tôi đang nhập một createClient
hàm từ Supabase và tạo một biến được gọi là supabase
. Chúng tôi gọi createClient
hàm và sau đó chuyển vào các tham số của chúng tôi: URL ( supabaseUrl
) và Anon Key ( supabaseKey
).
Bây giờ, chúng ta có thể gọi và sử dụng ứng dụng khách Supabase ở bất kỳ đâu trong dự án của mình!
Trước tiên, chúng ta cần định cấu hình ứng dụng của mình theo cách chúng ta muốn. Chúng tôi sẽ có một thanh điều hướng với tên dự án và các tùy chọn Đăng nhập và Đăng ký khi ứng dụng được tải lần đầu tiên. Khi người dùng đăng ký và đăng nhập, chúng tôi sẽ hiển thị thanh điều hướng để có các nút Home , Logout và Create Workout .
Cũng sẽ có một chân trang trên mỗi trang trên trang web.
Để làm điều này, chúng tôi sẽ tạo một component
thư mục chứa Navbar.js
và Footer.js
các tệp. Sau đó, bên trong _app.js
, chúng tôi sẽ bọc pages
thành phần của mình bằng Navbar
và các Footer
thành phần để chúng được hiển thị trên mọi trang của ứng dụng.
// _app.js
import Footer from "../components/Footer";
import Navbar from "../components/Navbar";
import "../styles/globals.css";
function MyApp({ Component, pageProps }) {
return (
<div>
<Navbar/>
<Component {...pageProps}/>
<Footer />
</div>
);
}
export default MyApp;
Tôi đã tạo ý chính GitHub ở đây để xem hai thành phần này trông như thế nào cùng với các kiểu tôi đã sử dụng.
Bây giờ, trang chủ của chúng tôi sẽ trông như thế này:
Để triển khai xác thực người dùng, chúng tôi sẽ khởi tạo trạng thái người dùng trong _app.js
tệp của mình và tạo một validateUser
hàm để kiểm tra và xác thực người dùng. Sau đó, chúng tôi sẽ đặt trạng thái người dùng thành đối tượng phiên được trả về.
// _app.js
import { useState, useEffect } from "react";
import Footer from "../components/Footer";
import Navbar from "../components/Navbar";
import "../styles/globals.css";
import { supabase } from "../utils/supabase";
function MyApp({ Component, pageProps }) {
const [session, setSession] = useState(null);
useEffect(() => {
setSession(supabase.auth.session());
supabase.auth.onAuthStateChange((_event, session) => {
setSession(session);
});
}, []);
return (
<div>
<Navbar session={session} />
<Component {...pageProps} session={session} />
<Footer />
</div>
);
}
export default MyApp;
Khi người dùng tải trang chủ của ứng dụng của chúng tôi, chúng tôi muốn hiển thị một nút để yêu cầu họ đăng nhập hoặc đăng ký. Khi nhấp vào nút Đăng nhập , nút này sẽ chuyển hướng người dùng đến một trang nơi người dùng có thể nhập email và mật khẩu của họ. Nếu họ là người dùng hiện tại và chi tiết đăng nhập hợp lệ, họ sẽ được chuyển hướng đến trang chủ.
Nếu người dùng có thông tin đăng nhập không hợp lệ, một thông báo cảnh báo sẽ hiển thị để cho người dùng biết về sự cố. Thay vào đó, họ sẽ được hiển thị một tùy chọn đăng ký.
Khi người dùng đăng ký, một email xác nhận sẽ được gửi đến email họ đã nhập. họ sẽ cần xác nhận email của mình bằng cách nhấp vào liên kết trong nội dung email.
Bây giờ, khi chúng tôi nhấp vào nút Đăng nhập , chúng tôi sẽ được chuyển hướng đến trang người dùng đến trang này:
Bây giờ, chúng ta có thể nhấp vào nút Đăng ký và nhập email.
Khi chúng tôi nhấp vào đây, một email sẽ được gửi để xác nhận địa chỉ email. Sau khi xác nhận, nó sẽ đăng nhập cho chúng tôi và chúng tôi sẽ thấy một trang như thế này:
Lưu ý rằng nếu chúng tôi chưa đăng nhập, chúng tôi không thể xem bảng điều khiển hoạt động của mình, thấy nút để tạo bài tập mới hoặc đăng xuất. Đây là xác thực được đề cập ban đầu được cung cấp cho chúng tôi bởi Supabase!
Bây giờ, chúng ta sẽ đi sâu vào việc tạo khả năng tạo, sửa đổi và xóa các bài tập của người dùng.
Chúng tôi sẽ cần tìm nạp tất cả các bài tập mà chúng tôi sẽ tạo và hiển thị chúng trên trang chủ. Chúng tôi sẽ thực hiện việc này bên trong index.js
tệp:
// /pages/index.js
import Head from "next/head";
import Link from "next/link";
import { useEffect, useState } from "react";
import styles from "../styles/Home.module.css";
import { supabase } from "../utils/supabase";
import WorkoutCard from "../components/WorkoutCard";
export default function Home({ session }) {
const [workouts, setWorkouts] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchWorkouts();
}, []);
const fetchWorkouts = async () => {
const user = supabase.auth.user();
try {
setLoading(true);
const { data, error } = await supabase
.from("workouts")
.select("*")
.eq("user_id", user?.id);
if (error) throw error;
setWorkouts(data);
} catch (error) {
alert(error.message);
} finally {
setLoading(false);
}
};
if (loading) {
return <div className={styles.loading}>Fetching Workouts...</div>;
}
return (
<div className={styles.container}>
<Head>
<title>Nextjs x Supabase</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<div className={styles.home}>
{!session?.user ? (
<div>
<p>
Welcome to Adrenargy. Kindly log in to your account or sign in for
a demo
</p>
</div>
) : (
<div>
<p className={styles.workoutHeading}>
Hello <span className={styles.email}>{session.user.email}</span>,
Welcome to your dashboard
</p>
{workouts?.length === 0 ? (
<div className={styles.noWorkout}>
<p>You have no workouts yet</p>
<Link href="/create">
<button className={styles.button}>
{" "}
Create a New Workout
</button>
</Link>
</div>
) : (
<div>
<p className={styles.workoutHeading}>Here are your workouts</p>
<WorkoutCard data={workouts}/>
</div>
)}
</div>
)}
</div>
</div>
);
}
Trong thành phần này, chúng tôi đang hủy cấu session
trúc đối tượng mà chúng tôi đã chuyển từ các page
đạo cụ trong _app.js
tệp và sử dụng đối tượng đó để xác thực người dùng được ủy quyền. Nếu không có người dùng, trang tổng quan sẽ không được hiển thị. Nếu có người dùng đăng nhập, bảng điều khiển các bài tập sẽ xuất hiện. Và nếu chưa có bài tập nào được tạo, một văn bản cho biết “Bạn chưa có bài tập nào” và nút để tạo một bài tập mới sẽ xuất hiện.
Để hiển thị các bài tập đã tạo của chúng tôi, chúng tôi có hai trạng thái:, workouts
một mảng trống và một loading
trạng thái nhận giá trị boolean là true
. Chúng tôi đang sử dụng useEffect
để tìm nạp dữ liệu tập luyện từ cơ sở dữ liệu khi trang được tải.
Hàm fetchWorkouts
được sử dụng để gọi cá thể Supabase để trả về tất cả dữ liệu từ các bảng tập luyện trong cơ sở dữ liệu của chúng tôi bằng select
phương pháp này. Các . eq()
phương pháp lọc được sử dụng để lọc ra và chỉ trả về dữ liệu có id người dùng phù hợp với người dùng đăng nhập hiện tại. Sau đó, setWorkouts
được đặt thành dữ liệu được gửi từ cơ sở dữ liệu và setLoading
được đặt trở lại sau false
khi chúng tôi tìm nạp dữ liệu của mình.
Nếu dữ liệu vẫn đang được tìm nạp, trang sẽ hiển thị “Tìm nạp các bài tập…” và nếu yêu cầu được thực hiện đến cơ sở dữ liệu của chúng tôi trả về mảng các bài tập của chúng tôi, chúng tôi muốn ánh xạ qua mảng và hiển thị WorkoutCard
thành phần.
Trong WorkoutCard
thành phần, chúng tôi đang hiển thị tiêu đề bài tập, tải, số lần tập và ngày giờ nó được tạo. Thời gian được tạo đang được định dạng bằng date-fns
thư viện mà bạn có thể xem tại đây . Chúng ta sẽ xem các thẻ của chúng ta trông như thế nào khi chúng ta bắt đầu tạo chúng trong phần tiếp theo.
// Workoutcard.js
import Link from "next/link";
import styles from "../styles/WorkoutCard.module.css";
import { BsTrash } from "react-icons/bs";
import { FiEdit } from "react-icons/fi";
import { formatDistanceToNow } from "date-fns/";
const WorkoutCard = ({ data }) => {
return (
<div className={styles.workoutContainer}>
{data?.map((item) => (
<div key={item.id} className={styles.container}>
<p className={styles.title}>
{" "}
Title: {""}
{item.title}
</p>
<p className={styles.load}>
{" "}
Load(kg): {" "}
{item.loads}
</p>
<p className={styles.reps}>Reps:{item.reps}</p>
<p className={styles.time}>
created:{" "}
{formatDistanceToNow(new Date(item.inserted_at), {
addSuffix: true,
})}
</p>
</div>
))}
</div>
);
};
export default WorkoutCard;
Bây giờ chúng tôi đã đăng nhập, trang tổng quan của chúng tôi mới và sạch sẽ. Để triển khai khả năng tạo một bài tập mới, chúng tôi sẽ thêm create.js
và Create.module.css
các tệp trong thư mục pages
và styles
tương ứng, đồng thời triển khai một số logic và kiểu dáng.
// /pages/create.js
import { supabase } from "../utils/supabase";
import { useState } from "react";
import styles from "../styles/Create.module.css";
import { useRouter } from "next/router";
const Create = () => {
const initialState = {
title: "",
loads: "",
reps: "",
};
const router = useRouter();
const [workoutData, setWorkoutData] = useState(initialState);
const { title, loads, reps } = workoutData;
const handleChange = (e) => {
setWorkoutData({ ...workoutData, [e.target.name]: e.target.value });
};
const createWorkout = async () => {
try {
const user = supabase.auth.user();
const { data, error } = await supabase
.from("workouts")
.insert([
{
title,
loads,
reps,
user_id: user?.id,
},
])
.single();
if (error) throw error;
alert("Workout created successfully");
setWorkoutData(initialState);
router.push("/");
} catch (error) {
alert(error.message);
}
};
return (
<>
<div className={styles.container}>
<div className={styles.form}>
<p className={styles.title}>Create a New Workout</p>
<label className={styles.label}>Title:</label>
<input
type="text"
name="title"
value={title}
onChange={handleChange}
className={styles.input}
placeholder="Enter a title"
/>
<label className={styles.label}>Load (kg):</label>
<input
type="text"
name="loads"
value={loads}
onChange={handleChange}
className={styles.input}
placeholder="Enter weight load"
/>
<label className={styles.label}>Reps:</label>
<input
type="text"
name="reps"
value={reps}
onChange={handleChange}
className={styles.input}
placeholder="Enter number of reps"
/>
<button className={styles.button} onClick={createWorkout}>
Create Workout
</button>
</div>
</div>
</>
);
};
export default Create;
Ở đây, phạm vi giao diện người dùng cơ bản là chúng ta sẽ có một biểu mẫu để tạo một bài tập mới. Biểu mẫu sẽ bao gồm ba trường (tiêu đề, tải và đại diện) như chúng tôi đã chỉ định khi tạo cơ sở dữ liệu của mình.
Một đối tượng trạng thái ban đầu được định nghĩa để xử lý tất cả các trường này đã được chuyển đến workoutsData
trạng thái. Hàm onChange
được sử dụng để xử lý các thay đổi của trường đầu vào.
Hàm createWorkout
sử dụng cá thể máy khách Supabase để tạo một bài tập mới bằng cách sử dụng các trường trạng thái ban đầu mà chúng tôi đã xác định và chèn nó vào bảng cơ sở dữ liệu.
Cuối cùng, chúng tôi có một bánh mì nướng thông báo cho chúng tôi biết khi bài tập mới của chúng tôi đã được tạo.
Sau đó, chúng tôi đặt dữ liệu biểu mẫu trở lại trạng thái chuỗi trống ban đầu khi bài tập của chúng tôi đã được tạo. Sau đó, chúng tôi đang sử dụng router.push
phương pháp này để điều hướng người dùng quay lại trang chủ.
Để cập nhật bài tập, chúng tôi sẽ tạo một thư mục có tên edit
trong pages
thư mục chứa [id].js
tệp của chúng tôi. Chúng tôi sẽ tạo một biểu tượng liên kết chỉnh sửa trên thẻ thành phần tập luyện của chúng tôi liên kết đến trang này. Khi các thẻ được hiển thị trên trang chủ, chúng ta có thể nhấp vào biểu tượng chỉnh sửa này và nó sẽ đưa chúng ta đến trang chỉnh sửa của thẻ cụ thể đó.
Sau đó, chúng tôi sẽ tìm nạp thông tin chi tiết của thẻ tập luyện cần thiết để được cập nhật từ bảng tập luyện của chúng tôi bởi id
chủ sở hữu được ủy quyền và của thẻ đó. Sau đó, chúng tôi sẽ tạo một updateWorkout
chức năng để cập nhật chi tiết thẻ tập luyện của chúng tôi:
// /pages/edit/[id].js
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import styles from "../../styles/Edit.module.css";
import { supabase } from "../../utils/supabase";
const Edit = () => {
const [workout, setWorkout] = useState("");
const router = useRouter();
const { id } = router.query;
useEffect(() => {
const user = supabase.auth.user();
const getWorkout = async () => {
const { data } = await supabase
.from("workouts")
.select("*")
.eq("user_id", user?.id)
.filter("id", "eq", id)
.single();
setWorkout(data);
};
getWorkout();
}, [id]);
const handleOnChange = (e) => {
setWorkout({
...workout,
[e.target.name]: e.target.value,
});
};
const { title, loads, reps } = workout;
const updateWorkout = async () => {
const user = supabase.auth.user();
const { data } = await supabase
.from("workouts")
.update({
title,
loads,
reps,
})
.eq("id", id)
.eq("user_id", user?.id);
alert("Workout updated successfully");
router.push("/");
};
return (
<div className={styles.container}>
<div className={styles.formContainer}>
<h1 className={styles.title}>Edit Workout</h1>
<label className={styles.label}> Title:</label>
<input
type="text"
name="title"
value={workout.title}
onChange={handleOnChange}
className={styles.updateInput}
/>
<label className={styles.label}> Load (kg):</label>
<input
type="text"
name="loads"
value={workout.loads}
onChange={handleOnChange}
className={styles.updateInput}
/>
<label className={styles.label}> Reps:</label>
<input
type="text"
name="reps"
value={workout.reps}
onChange={handleOnChange}
className={styles.updateInput}
/>
<button onClick={updateWorkout} className={styles.updateButton}>
Update Workout
</button>
</div>
</div>
);
};
export default Edit;
Đầu tiên, chúng tôi tạo một trạng thái để lưu trữ các chi tiết thẻ tập luyện sẽ được tìm nạp từ bảng của chúng tôi. Sau đó, chúng tôi trích xuất id
thẻ của thẻ đó bằng cách sử dụng useRouter
hook. Hàm getWorkout
gọi phiên bản máy khách Supabase để lọc id
thẻ tập luyện đó và trả về dữ liệu (tiêu đề, số lần tải và số lần thực hiện).
Khi chi tiết thẻ tập luyện đã được trả lại, chúng tôi có thể tạo updateWorkout
chức năng của mình để sửa đổi các chi tiết bằng cách sử dụng .update()
chức năng. Sau khi người dùng cập nhật bài tập và nhấp vào nút Cập nhật bài tập , một thông báo cảnh báo sẽ được gửi và người dùng sẽ được chuyển hướng trở lại trang chủ.
Hãy xem nó hoạt động như thế nào.
Nhấp vào biểu tượng chỉnh sửa để chuyển đến trang chỉnh sửa. Chúng tôi sẽ đổi tên tiêu đề từ “Dumbell Press” thành “Arm Curl”:
Để xóa một bài tập trên mỗi thẻ, chúng tôi sẽ tạo handleDelete
hàm sẽ lấy id
làm đối số. Chúng tôi sẽ gọi phiên bản Supabase để xóa thẻ tập luyện bằng cách sử dụng
.delete()
hàm số. Điều này .eq('id', id)
chỉ định id
hàng sẽ bị xóa trên bảng.
const handleDelete = async (id) => {
try {
const user = supabase.auth.user();
const { data, error } = await supabase
.from("workouts")
.delete()
.eq("id", id)
.eq("user_id", user?.id);
fetchWorkouts();
alert("Workout deleted successfully");
} catch (error) {
alert(error.message);
}
};
eq('user_id', user?.id)
Được sử dụng để kiểm tra xem thẻ đang bị xóa có thuộc về người dùng cụ thể đó hay không . Hàm sẽ được chuyển đến WorkoutCard
thành phần trong index.js
tệp và bị hủy để sử dụng trong chính thành phần đó như sau:
const WorkoutCard = ({ data, handleDelete }) => {
return (
<div className={styles.workoutContainer}>
{data?.map((item) => (
<div key={item.id} className={styles.container}>
<p className={styles.title}>
{" "}
Title: {""}
{item.title}
</p>
<p className={styles.load}>
{" "}
Load(kg): {" "}
{item.loads}
</p>
<p className={styles.reps}>Reps:{item.reps}</p>
<p className={styles.time}>
created:{" "}
{formatDistanceToNow(new Date(item.inserted_at), {
addSuffix: true,
})}
</p>
<div className={styles.buttons}>
<Link href={`/edit/${item.id}`}>
<a className={styles.edit}>
<FiEdit />
</a>
</Link>
<button
onClick={() => handleDelete(item.id)}
className={styles.delete}
>
<BsTrash />
</button>
</div>
</div>
))}
</div>
);
};
Bánh mì nướng cảnh báo sẽ được hiển thị khi thẻ đã được xóa thành công và người dùng sẽ được chuyển hướng đến trang chủ.
Bây giờ, chúng tôi phải triển khai ứng dụng của mình cho Vercel để bất kỳ ai trên Internet đều có thể sử dụng nó!
Để triển khai Vercel, trước tiên bạn phải đẩy mã của mình vào kho lưu trữ, đăng nhập vào bảng điều khiển Vercel, nhấp vào Tạo dự án mới và nhấp vào kho lưu trữ mà bạn vừa đẩy mã của mình vào.
Nhập các biến môi trường mà chúng tôi đã tạo trước đó cùng với các giá trị của chúng ( NEXT_PUBLIC_SUPABASE_URL
và NEXT_PUBLIC_SUPABASE_ANON_KEY
) trong trường Biến môi trường và nhấp vào Triển khai để triển khai ứng dụng của bạn vào phiên bản sản xuất.
Và chúng tôi đã có nó!
Cảm ơn bạn đã đọc! Tôi hy vọng hướng dẫn này cung cấp cho bạn kiến thức bắt buộc cần thiết để tạo ứng dụng toàn ngăn xếp bằng Next.js và Supabase.
Bạn có thể tùy chỉnh kiểu cho trường hợp sử dụng của mình, vì hướng dẫn này chủ yếu tập trung vào logic của việc tạo một ứng dụng đầy đủ.
Nguồn: https://blog.logrocket.com/build-full-stack-app-next-js-supabase/
1660881600
Lorsqu'il s'agit de créer et de choisir des frameworks pour votre prochaine application full-stack, combiner Next.js avec Supabase est l'une des meilleures options avec lesquelles travailler à mon avis.
Supabase est une alternative open source à Firebase avec de nombreux outils puissants, y compris une authentification transparente . En tant que développeur, cela est essentiel pour créer une application complète réussie.
Outre l'authentification, Supabase est livré avec d'autres fonctionnalités, telles qu'une base de données Postgres, des abonnements en temps réel et un stockage d'objets. Je pense que Supabase est l'un des backend-as-a-services les plus faciles à démarrer ou à intégrer.
Dans cet article, nous allons apprendre à créer une application full-stack en utilisant Next.js et Supabase . Nous verrons comment configurer un projet Supabase, configurer l'interface utilisateur et implémenter l'authentification et les fonctionnalités.
Le concept de cette application est que les utilisateurs puissent suivre et créer des activités d'entraînement basées sur des paramètres spécifiés, modifier ces activités s'il y a des erreurs ou des modifications nécessaires, et les supprimer si nécessaire. Commençons!
Next.js est l'un des moyens les plus simples et les plus populaires de créer des applications React prêtes pour la production. Ces dernières années, Next.js a connu une croissance exponentielle importante et de nombreuses entreprises l'ont adopté pour construire leurs applications.
Supabase est une alternative open source sans serveur à Firebase construite sur la base de données PostgreSQL. Il fournit tous les services backend nécessaires pour créer une application complète.
En tant qu'utilisateur, vous pouvez gérer votre base de données à partir de l'interface Supabase, allant de la création de tables et de relations à l'écriture de vos requêtes SQL et de votre moteur en temps réel sur PostgreSQL.
Supabase est livré avec des fonctionnalités vraiment intéressantes qui facilitent encore plus le développement de votre application full-stack. Certaines de ces fonctionnalités sont :
auth.users
table dès que vous créez votre base de données. Lorsque vous créez une application, Supabase attribue également un utilisateur et un identifiant dès que vous vous inscrivez sur l'application qui peut être référencée dans la base de données. Pour les méthodes de connexion, il existe différentes manières d'authentifier les utilisateurs, telles que l'e-mail, le mot de passe, les liens magiques, Google, GitHub, etc.Pour lancer notre projet dans le terminal avec le template Next.js, nous allons exécuter la commande suivante :
npx create-next-app nextjs-supabase
nextjs-supabase
est le nom du dossier de notre application où nous engloberons le modèle d'application Next.js.
Nous devrons installer le package client Supabase pour nous connecter ultérieurement à notre application Next.js. Nous pouvons le faire en exécutant l'une des commandes suivantes :
yarn add @supabase/supabase-js
ou
npm i @supabase/supabase-js
Une fois l'application terminée, ouvrez le dossier dans votre éditeur de code préféré. Maintenant, nous pouvons supprimer le modèle de base de notre /pages/index.js
fichier et le remplacer par un h1
titre indiquant "Bienvenue dans l'application d'entraînement".
Une fois cela fait, exécutez la commande yarn dev
dans le terminal pour démarrer votre application à http://localhost:3000 . Vous devriez voir une page comme celle-ci :
Pour configurer un projet Supabase, visitez app.supabase.com pour vous connecter au tableau de bord de l'application à l'aide de votre compte GitHub.
Une fois connecté, vous pouvez créer votre organisation et configurer un nouveau projet au sein de celle-ci en cliquant sur Tous les projets .
Cliquez sur Nouveau projet et donnez à votre projet un nom et un mot de passe de base de données. Cliquez sur le bouton Créer un nouveau projet ; il faudra quelques minutes pour que votre projet soit opérationnel.
Une fois le projet créé, vous devriez voir un tableau de bord comme celui-ci :
Pour ce tutoriel, j'ai déjà créé un projet nomméworkout-next-supabase.
Maintenant, créons notre table de base de données en cliquant sur l' icône de l' éditeur SQL sur notre tableau de bord et en cliquant sur Nouvelle requête . Entrez la requête SQL ci-dessous dans l'éditeur et cliquez sur RUN pour exécuter la requête.
CREATE TABLE workouts (
id bigint generated by default as identity primary key,
user_id uuid references auth.users not null,
user_email text,
title text,
loads text,
reps text,
inserted_at timestamp with time zone default timezone('utc'::text, now()) not null
);
alter table workouts enable row level security;
create policy "Individuals can create workouts." on workouts for
insert with check (auth.uid() = user_id);
create policy "Individuals can update their own workouts." on workouts for
update using (auth.uid() = user_id);
create policy "Individuals can delete their own workouts." on workouts for
delete using (auth.uid() = user_id);
create policy "Workouts are public." on workouts for
select using (true);
Cela créera la table d'entraînement que nous utiliserons pour créer notre application CRUD.
Parallèlement à la création d'un tableau, des autorisations au niveau des lignes seront activées pour garantir que seuls les utilisateurs autorisés peuvent créer, mettre à jour ou supprimer les détails de leurs entraînements.
Pour voir à quoi ressemble le tableau d'entraînement, nous pouvons cliquer sur l' icône de l' éditeur de tableau sur le tableau de bord pour voir le tableau d'entraînement que nous venons de créer.
Pour cette application, nous aurons sept colonnes :
user_id
user_email
id
title
loads
reps
Date stamp
Une fois notre table et nos colonnes définies, l'étape suivante consiste à connecter notre base de données Supabase à notre application frontale Next.js !
Pour connecter Supabase à notre application Next.js, nous aurons besoin de notre URL de projet et de notre clé Anon . Ces deux éléments peuvent être trouvés sur notre tableau de bord de base de données. Pour obtenir ces deux clés, cliquez sur l'icône d'engrenage pour aller dans Paramètres puis cliquez sur API . Vous verrez ces deux clés apparaître comme ceci :
Bien sûr, nous ne voulons pas exposer ces valeurs publiquement sur le navigateur ou notre référentiel car il s'agit d'informations sensibles. À notre avantage, Next.js fournit un support intégré pour les variables d'environnement qui nous permettent de créer un .env.local
fichier à la racine de notre projet. Cela chargera nos variables d'environnement et les exposera au navigateur en le préfixant avec NEXT_PUBLIC
.
Maintenant, créons un .env.local
fichier à la racine de notre projet et incluons notre URL et nos clés dans le fichier.
.env.local NEXT_PUBLIC_SUPABASE_URL= // collez l'url de votre projet ici NEXT_PUBLIC_SUPABASE_ANON_KEY= // collez votre clé supabase anon ici
NB, n'oubliez pas d'inclure
.env.local
dans votregitignore
fichier pour éviter qu'il ne soit poussé vers le référentiel GitHub (et disponible pour tout le monde) lors du déploiement.
Créons maintenant notre fichier client Supabase en créant un fichier appelé supabase.js
à la racine de notre projet. Dans le supabase.js
fichier, nous écrirons le code suivant :
// supabase.js
import { createClient } from "@supabase/supabase-js";
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
export const supabase = createClient(supabaseUrl, supabaseKey);
Ici, nous importons une createClient
fonction de Supabase et créons une variable appelée supabase
. Nous appelons la createClient
fonction puis passons nos paramètres : URL ( supabaseUrl
) et Anon Key ( supabaseKey
).
Maintenant, nous pouvons appeler et utiliser le client Supabase n'importe où dans notre projet !
Tout d'abord, nous devons configurer notre application pour qu'elle ressemble à ce que nous voulons. Nous aurons une barre de navigation avec le nom du projet et les options de connexion et d' inscription lors du premier chargement de l'application. Lorsqu'un utilisateur s'inscrit et se connecte, nous afficherons la barre de navigation avec les boutons Accueil , Déconnexion et Créer un entraînement .
Il y aura également un pied de page sur chaque page du site Web.
Pour ce faire, nous allons créer un component
dossier qui contiendra les fichiers Navbar.js
et . Footer.js
Ensuite, à l'intérieur _app.js
de , nous encapsulerons notre pages
composant avec les Navbar
composants et Footer
afin qu'ils soient affichés sur chaque page de l'application.
// _app.js
import Footer from "../components/Footer";
import Navbar from "../components/Navbar";
import "../styles/globals.css";
function MyApp({ Component, pageProps }) {
return (
<div>
<Navbar/>
<Component {...pageProps}/>
<Footer />
</div>
);
}
export default MyApp;
J'ai créé un GitHub Gist ici pour voir à quoi ressemblent ces deux composants aux côtés des styles que j'ai utilisés.
Maintenant, notre page d'accueil devrait ressembler à ceci :
Pour implémenter l'authentification des utilisateurs, nous allons initialiser l'état de l'utilisateur dans notre _app.js
fichier et créer une validateUser
fonction pour vérifier et valider un utilisateur. Nous définirons ensuite l'état de l'utilisateur sur l'objet de session renvoyé.
// _app.js
import { useState, useEffect } from "react";
import Footer from "../components/Footer";
import Navbar from "../components/Navbar";
import "../styles/globals.css";
import { supabase } from "../utils/supabase";
function MyApp({ Component, pageProps }) {
const [session, setSession] = useState(null);
useEffect(() => {
setSession(supabase.auth.session());
supabase.auth.onAuthStateChange((_event, session) => {
setSession(session);
});
}, []);
return (
<div>
<Navbar session={session} />
<Component {...pageProps} session={session} />
<Footer />
</div>
);
}
export default MyApp;
Lorsqu'un utilisateur charge la page d'accueil de notre application, nous voulons afficher un bouton pour lui dire de se connecter ou de s'inscrire. Lorsque le bouton de connexion est cliqué, il doit rediriger l'utilisateur vers une page où l'utilisateur peut saisir son adresse e-mail et son mot de passe. S'il s'agit d'un utilisateur existant et que les informations de connexion sont valides, il sera redirigé vers la page d'accueil.
Si l'utilisateur a des informations d'identification non valides, un message d'alerte s'affichera pour informer l'utilisateur du problème. Une option d'inscription s'affichera à la place.
Lorsque l'utilisateur s'inscrit, un e-mail de confirmation est envoyé à l'adresse e-mail saisie. ils devront confirmer leur e-mail en cliquant sur le lien dans le corps de l'e-mail.
Maintenant, lorsque nous cliquons sur le bouton Connexion , nous devrions être redirigés vers la page utilisateur vers cette page :
Maintenant, nous pouvons cliquer sur le bouton S'inscrire et entrer un e-mail.
Une fois que nous avons cliqué dessus, un e-mail sera envoyé pour confirmer l'adresse e-mail. Après confirmation, il nous connectera et nous devrions voir une page comme celle-ci :
Notez que si nous ne nous sommes pas connectés, nous ne pouvons pas voir notre tableau de bord d'activité, voir un bouton pour créer un nouvel entraînement ou nous déconnecter. C'est l'authentification mentionnée initialement qui nous est fournie par Supabase !
Maintenant, nous allons plonger dans la création de la capacité d'un utilisateur à créer, modifier et supprimer ses entraînements.
Nous devrons récupérer tous les entraînements que nous allons créer et les afficher sur la page d'accueil. Nous allons le faire à l'intérieur du index.js
fichier :
// /pages/index.js
import Head from "next/head";
import Link from "next/link";
import { useEffect, useState } from "react";
import styles from "../styles/Home.module.css";
import { supabase } from "../utils/supabase";
import WorkoutCard from "../components/WorkoutCard";
export default function Home({ session }) {
const [workouts, setWorkouts] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchWorkouts();
}, []);
const fetchWorkouts = async () => {
const user = supabase.auth.user();
try {
setLoading(true);
const { data, error } = await supabase
.from("workouts")
.select("*")
.eq("user_id", user?.id);
if (error) throw error;
setWorkouts(data);
} catch (error) {
alert(error.message);
} finally {
setLoading(false);
}
};
if (loading) {
return <div className={styles.loading}>Fetching Workouts...</div>;
}
return (
<div className={styles.container}>
<Head>
<title>Nextjs x Supabase</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<div className={styles.home}>
{!session?.user ? (
<div>
<p>
Welcome to Adrenargy. Kindly log in to your account or sign in for
a demo
</p>
</div>
) : (
<div>
<p className={styles.workoutHeading}>
Hello <span className={styles.email}>{session.user.email}</span>,
Welcome to your dashboard
</p>
{workouts?.length === 0 ? (
<div className={styles.noWorkout}>
<p>You have no workouts yet</p>
<Link href="/create">
<button className={styles.button}>
{" "}
Create a New Workout
</button>
</Link>
</div>
) : (
<div>
<p className={styles.workoutHeading}>Here are your workouts</p>
<WorkoutCard data={workouts}/>
</div>
)}
</div>
)}
</div>
</div>
);
}
Dans ce composant, nous détruisons l' session
objet que nous avons transmis à partir des page
accessoires du _app.js
fichier et l'utilisons pour valider les utilisateurs autorisés. S'il n'y a pas d'utilisateurs, le tableau de bord ne s'affichera pas. Si un utilisateur est connecté, le tableau de bord des entraînements apparaîtra. Et s'il n'y a pas d'entraînement créé, un texte disant "Vous n'avez pas encore d'entraînement" et un bouton pour en créer un nouveau apparaîtront.
Pour rendre nos entraînements créés, nous avons deux états : workouts
, un tableau vide et un loading
état qui prend une valeur booléenne de true
. Nous utilisons useEffect
pour récupérer les données d'entraînement de la base de données lorsque la page est chargée.
La fetchWorkouts
fonction est utilisée pour appeler l'instance Supabase pour renvoyer toutes les données des tables d'entraînement dans notre base de données à l'aide de la select
méthode. La . eq()
La méthode filter est utilisée pour filtrer et renvoyer uniquement les données dont l'ID utilisateur correspond à l'utilisateur actuellement connecté. Ensuite, setWorkouts
est défini sur les données envoyées à partir de la base de données et setLoading
est rétabli false
une fois que nous avons récupéré nos données.
Si les données sont toujours en cours de récupération, la page doit afficher "Fetching Workouts…" et si la demande faite à notre base de données renvoie le tableau de nos entraînements, nous voulons mapper le tableau et rendre le WorkoutCard
composant.
Dans le WorkoutCard
composant, nous rendons le titre de l'entraînement, la charge, les répétitions, ainsi que la date et l'heure de sa création. L'heure créée est formatée à l'aide de la date-fns
bibliothèque que vous pouvez consulter ici . Nous verrons à quoi ressemblent nos cartes lorsque nous commencerons à les créer dans la section suivante.
// Workoutcard.js
import Link from "next/link";
import styles from "../styles/WorkoutCard.module.css";
import { BsTrash } from "react-icons/bs";
import { FiEdit } from "react-icons/fi";
import { formatDistanceToNow } from "date-fns/";
const WorkoutCard = ({ data }) => {
return (
<div className={styles.workoutContainer}>
{data?.map((item) => (
<div key={item.id} className={styles.container}>
<p className={styles.title}>
{" "}
Title: {""}
{item.title}
</p>
<p className={styles.load}>
{" "}
Load(kg): {" "}
{item.loads}
</p>
<p className={styles.reps}>Reps:{item.reps}</p>
<p className={styles.time}>
created:{" "}
{formatDistanceToNow(new Date(item.inserted_at), {
addSuffix: true,
})}
</p>
</div>
))}
</div>
);
};
export default WorkoutCard;
Maintenant que nous sommes connectés, notre tableau de bord est frais et propre. Pour implémenter la possibilité de créer un nouvel entraînement, nous ajouterons des fichiers create.js
et dans le dossier et respectivement, et implémenterons une logique et un style.Create.module.csspagesstyles
// /pages/create.js
import { supabase } from "../utils/supabase";
import { useState } from "react";
import styles from "../styles/Create.module.css";
import { useRouter } from "next/router";
const Create = () => {
const initialState = {
title: "",
loads: "",
reps: "",
};
const router = useRouter();
const [workoutData, setWorkoutData] = useState(initialState);
const { title, loads, reps } = workoutData;
const handleChange = (e) => {
setWorkoutData({ ...workoutData, [e.target.name]: e.target.value });
};
const createWorkout = async () => {
try {
const user = supabase.auth.user();
const { data, error } = await supabase
.from("workouts")
.insert([
{
title,
loads,
reps,
user_id: user?.id,
},
])
.single();
if (error) throw error;
alert("Workout created successfully");
setWorkoutData(initialState);
router.push("/");
} catch (error) {
alert(error.message);
}
};
return (
<>
<div className={styles.container}>
<div className={styles.form}>
<p className={styles.title}>Create a New Workout</p>
<label className={styles.label}>Title:</label>
<input
type="text"
name="title"
value={title}
onChange={handleChange}
className={styles.input}
placeholder="Enter a title"
/>
<label className={styles.label}>Load (kg):</label>
<input
type="text"
name="loads"
value={loads}
onChange={handleChange}
className={styles.input}
placeholder="Enter weight load"
/>
<label className={styles.label}>Reps:</label>
<input
type="text"
name="reps"
value={reps}
onChange={handleChange}
className={styles.input}
placeholder="Enter number of reps"
/>
<button className={styles.button} onClick={createWorkout}>
Create Workout
</button>
</div>
</div>
</>
);
};
export default Create;
Ici, la portée de base de l'interface utilisateur est que nous aurons un formulaire pour créer un nouvel entraînement. Le formulaire sera composé de trois champs (titre, charge et reps) comme nous l'avons spécifié lors de la création de notre base de données.
Un objet d'état initial est défini pour gérer tous ces champs qui ont été passés à l' workoutsData
état. La onChange
fonction est utilisée pour gérer les modifications du champ de saisie.
La createWorkout
fonction utilise l'instance du client Supabase pour créer un nouvel entraînement à l'aide des champs d'état initiaux que nous avons définis et l'insère dans la table de la base de données.
Enfin, nous avons un toast d'alerte qui nous informe lorsque notre nouvel entraînement a été créé.
Ensuite, nous remettons les données du formulaire à l'état initial de la chaîne vide une fois que notre entraînement a été créé. Après cela, nous utilisons la router.push
méthode pour ramener l'utilisateur à la page d'accueil.
Pour mettre à jour un entraînement, nous allons créer un dossier appelé edit
dans notre pages
dossier qui contiendra notre [id].js
fichier. Nous allons créer une icône de lien de modification sur notre fiche de composant d'entraînement qui renvoie à cette page. Lorsque les cartes sont rendues sur la page d'accueil, nous pouvons cliquer sur cette icône d'édition et cela nous amènera à la page d'édition de cette carte particulière.
Nous allons ensuite récupérer les détails de la carte d'entraînement nécessaire à mettre à jour à partir de notre table d'entraînement par son id
propriétaire et le propriétaire autorisé de la carte. Ensuite, nous allons créer une updateWorkout
fonction pour mettre à jour les détails de notre carte d'entraînement :
// /pages/edit/[id].js
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import styles from "../../styles/Edit.module.css";
import { supabase } from "../../utils/supabase";
const Edit = () => {
const [workout, setWorkout] = useState("");
const router = useRouter();
const { id } = router.query;
useEffect(() => {
const user = supabase.auth.user();
const getWorkout = async () => {
const { data } = await supabase
.from("workouts")
.select("*")
.eq("user_id", user?.id)
.filter("id", "eq", id)
.single();
setWorkout(data);
};
getWorkout();
}, [id]);
const handleOnChange = (e) => {
setWorkout({
...workout,
[e.target.name]: e.target.value,
});
};
const { title, loads, reps } = workout;
const updateWorkout = async () => {
const user = supabase.auth.user();
const { data } = await supabase
.from("workouts")
.update({
title,
loads,
reps,
})
.eq("id", id)
.eq("user_id", user?.id);
alert("Workout updated successfully");
router.push("/");
};
return (
<div className={styles.container}>
<div className={styles.formContainer}>
<h1 className={styles.title}>Edit Workout</h1>
<label className={styles.label}> Title:</label>
<input
type="text"
name="title"
value={workout.title}
onChange={handleOnChange}
className={styles.updateInput}
/>
<label className={styles.label}> Load (kg):</label>
<input
type="text"
name="loads"
value={workout.loads}
onChange={handleOnChange}
className={styles.updateInput}
/>
<label className={styles.label}> Reps:</label>
<input
type="text"
name="reps"
value={workout.reps}
onChange={handleOnChange}
className={styles.updateInput}
/>
<button onClick={updateWorkout} className={styles.updateButton}>
Update Workout
</button>
</div>
</div>
);
};
export default Edit;
Tout d'abord, nous créons un état pour stocker les détails de la carte d'entraînement qui seront extraits de notre table. Ensuite, on extrait le id
de cette carte à l'aide du useRouter
crochet. La getWorkout
fonction appelle l'instance du client Supabase pour filtrer la id
carte d'entraînement et renvoie les données (titre, charges et répétitions).
Une fois les détails de la carte d'entraînement renvoyés, nous pouvons créer notre updateWorkout
fonction pour modifier les détails à l'aide de la .update()
fonction. Une fois que l'entraînement a été mis à jour par l'utilisateur et que le bouton Mettre à jour l'entraînement est cliqué, un message d'alerte est envoyé et l'utilisateur sera redirigé vers la page d'accueil.
Voyons voir comment ça fonctionne.
Cliquez sur l'icône d'édition pour accéder à la page d'édition. Nous allons renommer le titre de "Dumbell Press" en "Arm Curl":
Pour supprimer un entraînement sur chaque carte, nous allons créer la handleDelete
fonction qui prendra le id
en argument. Nous appellerons l'instance Supabase pour supprimer une carte d'entraînement à l'aide de la
.delete()
fonction. Ceci .eq('id', id)
spécifie la id
ligne à supprimer sur la table.
const handleDelete = async (id) => {
try {
const user = supabase.auth.user();
const { data, error } = await supabase
.from("workouts")
.delete()
.eq("id", id)
.eq("user_id", user?.id);
fetchWorkouts();
alert("Workout deleted successfully");
} catch (error) {
alert(error.message);
}
};
Le eq('user_id', user?.id)
est utilisé pour vérifier si la carte qui est supprimée appartient à cet utilisateur particulier. La fonction sera transmise au WorkoutCard
composant dans le index.js
fichier et déstructurée pour être utilisée dans le composant lui-même comme suit :
const WorkoutCard = ({ data, handleDelete }) => {
return (
<div className={styles.workoutContainer}>
{data?.map((item) => (
<div key={item.id} className={styles.container}>
<p className={styles.title}>
{" "}
Title: {""}
{item.title}
</p>
<p className={styles.load}>
{" "}
Load(kg): {" "}
{item.loads}
</p>
<p className={styles.reps}>Reps:{item.reps}</p>
<p className={styles.time}>
created:{" "}
{formatDistanceToNow(new Date(item.inserted_at), {
addSuffix: true,
})}
</p>
<div className={styles.buttons}>
<Link href={`/edit/${item.id}`}>
<a className={styles.edit}>
<FiEdit />
</a>
</Link>
<button
onClick={() => handleDelete(item.id)}
className={styles.delete}
>
<BsTrash />
</button>
</div>
</div>
))}
</div>
);
};
Un toast d'alerte s'affichera une fois la carte supprimée avec succès et l'utilisateur sera redirigé vers la page d'accueil.
Maintenant, nous devons déployer notre application sur Vercel pour que n'importe qui sur Internet puisse l'utiliser !
Pour déployer sur Vercel, vous devez d'abord pousser votre code vers votre référentiel, vous connecter à votre tableau de bord Vercel, cliquer sur Créer un nouveau projet et cliquer sur le référentiel dans lequel vous venez de pousser votre code.
Entrez les variables d'environnement que nous avons créées précédemment avec leurs valeurs ( NEXT_PUBLIC_SUPABASE_URL
et NEXT_PUBLIC_SUPABASE_ANON_KEY
) dans le champ Variable d'environnement et cliquez sur Déployer pour déployer votre application en production.
Et là, nous l'avons!
Merci pour la lecture! J'espère que ce tutoriel vous donnera les connaissances nécessaires pour créer une application full-stack en utilisant Next.js et Supabase.
Vous pouvez personnaliser le style en fonction de votre cas d'utilisation, car ce didacticiel se concentre principalement sur la logique de création d'une application complète.
Source : https://blog.logrocket.com/build-full-stack-app-next-js-supabase/
1660879800
在为您的下一个全栈应用程序构建和选择框架时,在我看来,将 Next.js 与 Supabase 结合使用是最好的选择之一。
Supabase 是一个开源的 Firebase 替代品,具有许多强大的工具,包括无缝身份验证。作为开发人员,这是构建成功的全栈应用程序的关键。
除了身份验证,Supabase 还具有其他功能,例如 Postgres 数据库、实时订阅和对象存储。我相信 Supabase 是最容易上手或集成的后端即服务之一。
在本文中,我们将学习如何使用 Next.js 和 Supabase 构建一个全栈应用程序。我们将讨论如何设置 Supbase 项目、配置 UI 以及实现身份验证和功能。
该应用程序的概念是让用户根据指定的参数跟踪和创建锻炼活动,如果有任何错误或必要的更改,则编辑这些活动,并在需要时将其删除。让我们开始吧!
Next.js 是构建生产就绪的 React 应用程序的最简单和最流行的方法之一。近年来,Next.js 经历了显着的指数级增长,许多公司都采用它来构建他们的应用程序。
Supabase 是基于 PostgreSQL 数据库构建的Firebase 的无服务器、开源替代品。它提供了创建全栈应用程序所需的所有后端服务。
作为用户,您可以从 Supbase 界面管理您的数据库,范围从创建表和关系到在 PostgreSQL 之上编写 SQL 查询和实时引擎。
Supabase 带有非常酷的功能,使您的全栈应用程序开发更加容易。其中一些功能包括:
auth.users
在您创建数据库后立即创建一个表。当您创建应用程序时,Supabase 也会在您注册应用程序后立即分配一个用户和 ID,该应用程序可以在数据库中引用。对于登录方法,您可以通过多种方式验证用户身份,例如电子邮件、密码、魔术链接、Google、GitHub 等要使用 Next.js 模板在终端中启动我们的项目,我们将运行以下命令:
npx create-next-app nextjs-supabase
nextjs-supabase
是我们应用程序的文件夹名称,我们将在其中包含 Next.js 应用程序模板。
稍后我们需要安装 Supabase 客户端包以连接到我们的 Next.js 应用程序。我们可以通过运行以下任一命令来做到这一点:
yarn add @supabase/supabase-js
或者
npm i @supabase/supabase-js
应用程序完成设置后,在您喜欢的代码编辑器中打开该文件夹。现在,我们可以删除/pages/index.js
文件中的基本模板,并将其替换为h1
标题为“欢迎使用 Workout App”。
完成后,在终端中运行命令以在http://localhost:3000yarn dev
启动您的应用程序。你应该看到这样的页面:
要设置 Supabase 项目,请访问app.supabase.com以使用您的 GitHub 帐户登录应用仪表板。
登录后,您可以通过单击All Projects创建您的组织并在其中设置一个新项目。
单击新建项目并为您的项目指定名称和数据库密码。单击创建新项目按钮;您的项目需要几分钟才能启动并运行。
创建项目后,您应该会看到如下所示的仪表板:
对于本教程,我已经创建了一个名为workout-next-supabase.
现在,让我们通过单击仪表板上的SQL 编辑器图标并单击New Query创建我们的数据库表。在编辑器中输入下面的 SQL 查询,然后单击RUN执行查询。
CREATE TABLE workouts (
id bigint generated by default as identity primary key,
user_id uuid references auth.users not null,
user_email text,
title text,
loads text,
reps text,
inserted_at timestamp with time zone default timezone('utc'::text, now()) not null
);
alter table workouts enable row level security;
create policy "Individuals can create workouts." on workouts for
insert with check (auth.uid() = user_id);
create policy "Individuals can update their own workouts." on workouts for
update using (auth.uid() = user_id);
create policy "Individuals can delete their own workouts." on workouts for
delete using (auth.uid() = user_id);
create policy "Workouts are public." on workouts for
select using (true);
这将创建我们将用于构建 CRUD 应用程序的锻炼表。
除了创建表格外,还将启用行级权限,以确保只有授权用户才能创建、更新或删除其锻炼的详细信息。
要查看锻炼表的外观,我们可以单击仪表板上的表格编辑器图标来查看我们刚刚创建的锻炼表。
对于这个应用程序,我们将有七列:
user_id
user_email
id
title
loads
reps
Date stamp
一旦我们的表和列设置好了,下一步就是将我们的 Supabase 数据库与我们的 Next.js 前端应用程序连接起来!
要将 Supabase 与我们的 Next.js 应用程序连接起来,我们将需要我们的项目 URL和Anon Key。这两个都可以在我们的数据库仪表板上找到。要获取这两个密钥,请单击齿轮图标转到设置,然后单击API。您会看到这两个键显示如下:
当然,我们不想在浏览器或我们的存储库上公开这些值,因为它是敏感信息。.env.local
对我们来说,Next.js 提供了对环境变量的内置支持,允许我们在项目的根目录中创建文件。这将加载我们的环境变量,并通过前缀将它们暴露给浏览器NEXT_PUBLIC
。
.env.local
现在,让我们在项目的根目录中创建一个文件,并在文件中包含我们的 URL 和密钥。
.env.local NEXT_PUBLIC_SUPABASE_URL= // 在此处粘贴您的项目 url NEXT_PUBLIC_SUPABASE_ANON_KEY= // 在此处粘贴您的 supbase 匿名密钥
注意,不要忘记包含
.env.local
在您的gitignore
文件中,以防止在部署时将其推送到 GitHub 存储库(并且可供所有人查看)。
supabase.js
现在让我们通过在项目的根目录创建一个名为的文件来创建我们的 Supabase 客户端文件。在该supabase.js
文件中,我们将编写以下代码:
// supabase.js
import { createClient } from "@supabase/supabase-js";
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
export const supabase = createClient(supabaseUrl, supabaseKey);
在这里,我们createClient
从 Supabase 导入一个函数并创建一个名为supabase
. 我们调用该createClient
函数,然后传入我们的参数:URL ( supabaseUrl
) 和 Anon Key ( supabaseKey
)。
现在,我们可以在项目的任何地方调用和使用 Supbase 客户端了!
首先,我们需要配置我们的应用程序以使其看起来像我们想要的那样。我们将有一个带有项目名称的导航栏,以及首次加载应用程序时的登录和注册选项。当用户注册并登录时,我们将显示导航栏,其中包含Home、Logout和Create Workout按钮。
网站上的每一页也会有一个页脚。
为此,我们将创建一个component
文件夹来存放Navbar.js
和Footer.js
文件。然后,在内部_app.js
,我们将pages
使用Navbar
和 组件包装我们的组件,Footer
以便它们显示在应用程序的每个页面上。
// _app.js
import Footer from "../components/Footer";
import Navbar from "../components/Navbar";
import "../styles/globals.css";
function MyApp({ Component, pageProps }) {
return (
<div>
<Navbar/>
<Component {...pageProps}/>
<Footer />
</div>
);
}
export default MyApp;
我在这里创建了一个 GitHub 要点,以查看这两个组件与我使用的样式一起看起来像什么。
现在,我们的主页应该是这样的:
为了实现用户身份验证,我们将在我们的_app.js
文件中初始化用户状态并创建一个validateUser
函数来检查和验证用户。然后我们将用户状态设置为返回的会话对象。
// _app.js
import { useState, useEffect } from "react";
import Footer from "../components/Footer";
import Navbar from "../components/Navbar";
import "../styles/globals.css";
import { supabase } from "../utils/supabase";
function MyApp({ Component, pageProps }) {
const [session, setSession] = useState(null);
useEffect(() => {
setSession(supabase.auth.session());
supabase.auth.onAuthStateChange((_event, session) => {
setSession(session);
});
}, []);
return (
<div>
<Navbar session={session} />
<Component {...pageProps} session={session} />
<Footer />
</div>
);
}
export default MyApp;
当用户加载我们应用程序的主页时,我们希望显示一个按钮来告诉他们登录或注册。单击登录按钮时,它应该将用户重定向到用户可以输入其电子邮件和密码的页面。如果他们是现有用户并且登录详细信息有效,他们将被重定向到主页。
如果用户的凭据无效,则会显示一条警报消息以告知用户该问题。他们将看到一个注册选项。
当用户注册时,确认电子邮件将发送到他们输入的电子邮件。他们需要通过单击电子邮件正文中的链接来确认他们的电子邮件。
现在,当我们点击登录按钮时,我们应该被重定向到用户页面到这个页面:
现在,我们可以单击“注册”按钮并输入电子邮件。
单击此按钮后,将发送一封电子邮件以确认电子邮件地址。确认后,它将让我们登录,我们应该看到如下页面:
请注意,如果我们尚未登录,我们将无法看到我们的活动仪表板、创建新锻炼的按钮或注销。这是最初提到的由 Supbase 提供给我们的身份验证!
现在,我们将深入探讨创建用户创建、修改和删除锻炼的能力。
我们需要获取我们将要创建的所有锻炼并将它们呈现在主页上。我们将在index.js
文件中执行此操作:
// /pages/index.js
import Head from "next/head";
import Link from "next/link";
import { useEffect, useState } from "react";
import styles from "../styles/Home.module.css";
import { supabase } from "../utils/supabase";
import WorkoutCard from "../components/WorkoutCard";
export default function Home({ session }) {
const [workouts, setWorkouts] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchWorkouts();
}, []);
const fetchWorkouts = async () => {
const user = supabase.auth.user();
try {
setLoading(true);
const { data, error } = await supabase
.from("workouts")
.select("*")
.eq("user_id", user?.id);
if (error) throw error;
setWorkouts(data);
} catch (error) {
alert(error.message);
} finally {
setLoading(false);
}
};
if (loading) {
return <div className={styles.loading}>Fetching Workouts...</div>;
}
return (
<div className={styles.container}>
<Head>
<title>Nextjs x Supabase</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<div className={styles.home}>
{!session?.user ? (
<div>
<p>
Welcome to Adrenargy. Kindly log in to your account or sign in for
a demo
</p>
</div>
) : (
<div>
<p className={styles.workoutHeading}>
Hello <span className={styles.email}>{session.user.email}</span>,
Welcome to your dashboard
</p>
{workouts?.length === 0 ? (
<div className={styles.noWorkout}>
<p>You have no workouts yet</p>
<Link href="/create">
<button className={styles.button}>
{" "}
Create a New Workout
</button>
</Link>
</div>
) : (
<div>
<p className={styles.workoutHeading}>Here are your workouts</p>
<WorkoutCard data={workouts}/>
</div>
)}
</div>
)}
</div>
</div>
);
}
在这个组件中,我们正在解构从文件中的 propssession
传递的对象,并使用它来验证授权用户。如果没有用户,则不会显示仪表板。如果有用户登录,则会出现锻炼仪表板。如果没有创建锻炼,则会出现一条“您还没有锻炼”的文字和一个创建新锻炼的按钮。page_app.js
为了渲染我们创建的锻炼,我们有两个状态:workouts
一个空数组和一个loading
接受布尔值的状态true
。我们useEffect
用于在页面加载时从数据库中获取锻炼数据。
该fetchWorkouts
函数用于调用 Supbase 实例以使用该select
方法从我们的数据库中的锻炼表中返回所有数据。这 。eq()
filter 方法用于过滤掉并仅返回用户 id 与当前登录用户匹配的数据。然后,setWorkouts
设置为从数据库发送的数据,并在我们获取数据setLoading
后设置回。false
如果仍在获取数据,则页面应显示“Fetching Workouts...”,并且如果向我们的数据库发出的请求返回我们的锻炼数组,我们希望通过该数组进行映射并渲染WorkoutCard
组件。
在该WorkoutCard
组件中,我们正在渲染锻炼标题、负荷、次数以及它的创建日期和时间。创建的时间正在使用您可以在此处查看date-fns
的库进行格式化。我们将在下一节开始创建卡片时查看卡片的外观。
// Workoutcard.js
import Link from "next/link";
import styles from "../styles/WorkoutCard.module.css";
import { BsTrash } from "react-icons/bs";
import { FiEdit } from "react-icons/fi";
import { formatDistanceToNow } from "date-fns/";
const WorkoutCard = ({ data }) => {
return (
<div className={styles.workoutContainer}>
{data?.map((item) => (
<div key={item.id} className={styles.container}>
<p className={styles.title}>
{" "}
Title: {""}
{item.title}
</p>
<p className={styles.load}>
{" "}
Load(kg): {" "}
{item.loads}
</p>
<p className={styles.reps}>Reps:{item.reps}</p>
<p className={styles.time}>
created:{" "}
{formatDistanceToNow(new Date(item.inserted_at), {
addSuffix: true,
})}
</p>
</div>
))}
</div>
);
};
export default WorkoutCard;
现在我们已经登录,我们的仪表板是新鲜和干净的。为了实现创建新锻炼的能力,我们将分别在和文件夹中添加create.js
和文件,并实现一些逻辑和样式。Create.module.csspagesstyles
// /pages/create.js
import { supabase } from "../utils/supabase";
import { useState } from "react";
import styles from "../styles/Create.module.css";
import { useRouter } from "next/router";
const Create = () => {
const initialState = {
title: "",
loads: "",
reps: "",
};
const router = useRouter();
const [workoutData, setWorkoutData] = useState(initialState);
const { title, loads, reps } = workoutData;
const handleChange = (e) => {
setWorkoutData({ ...workoutData, [e.target.name]: e.target.value });
};
const createWorkout = async () => {
try {
const user = supabase.auth.user();
const { data, error } = await supabase
.from("workouts")
.insert([
{
title,
loads,
reps,
user_id: user?.id,
},
])
.single();
if (error) throw error;
alert("Workout created successfully");
setWorkoutData(initialState);
router.push("/");
} catch (error) {
alert(error.message);
}
};
return (
<>
<div className={styles.container}>
<div className={styles.form}>
<p className={styles.title}>Create a New Workout</p>
<label className={styles.label}>Title:</label>
<input
type="text"
name="title"
value={title}
onChange={handleChange}
className={styles.input}
placeholder="Enter a title"
/>
<label className={styles.label}>Load (kg):</label>
<input
type="text"
name="loads"
value={loads}
onChange={handleChange}
className={styles.input}
placeholder="Enter weight load"
/>
<label className={styles.label}>Reps:</label>
<input
type="text"
name="reps"
value={reps}
onChange={handleChange}
className={styles.input}
placeholder="Enter number of reps"
/>
<button className={styles.button} onClick={createWorkout}>
Create Workout
</button>
</div>
</div>
</>
);
};
export default Create;
在这里,基本的 UI 范围是我们将有一个表单来创建一个新的锻炼。该表单将包含我们在创建数据库时指定的三个字段(标题、负载和代表)。
定义了一个初始状态对象来处理所有这些传递给workoutsData
状态的字段。该onChange
函数用于处理输入字段的更改。
该createWorkout
函数使用 Supbase 客户端实例使用我们定义的初始状态字段创建新的锻炼并将其插入到数据库表中。
最后,当我们创建了新的锻炼时,我们有一个警报 toast 通知我们。
然后,一旦我们的锻炼被创建,我们将表单数据设置回初始的空字符串状态。之后,我们使用该router.push
方法将用户导航回主页。
要更新锻炼,我们将edit
在我们的文件夹中创建一个名为的文件夹,该文件pages
夹将保存我们的[id].js
文件。我们将在链接到此页面的锻炼组件卡上创建一个编辑链接图标。当卡片呈现在主页上时,我们可以单击此编辑图标,它将带我们进入该特定卡片的编辑页面。
然后,我们将获取所需锻炼卡的详细信息,以由其id
和卡的授权所有者从我们的锻炼表中更新。然后,我们将创建一个updateWorkout
函数来更新我们的锻炼卡详细信息:
// /pages/edit/[id].js
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import styles from "../../styles/Edit.module.css";
import { supabase } from "../../utils/supabase";
const Edit = () => {
const [workout, setWorkout] = useState("");
const router = useRouter();
const { id } = router.query;
useEffect(() => {
const user = supabase.auth.user();
const getWorkout = async () => {
const { data } = await supabase
.from("workouts")
.select("*")
.eq("user_id", user?.id)
.filter("id", "eq", id)
.single();
setWorkout(data);
};
getWorkout();
}, [id]);
const handleOnChange = (e) => {
setWorkout({
...workout,
[e.target.name]: e.target.value,
});
};
const { title, loads, reps } = workout;
const updateWorkout = async () => {
const user = supabase.auth.user();
const { data } = await supabase
.from("workouts")
.update({
title,
loads,
reps,
})
.eq("id", id)
.eq("user_id", user?.id);
alert("Workout updated successfully");
router.push("/");
};
return (
<div className={styles.container}>
<div className={styles.formContainer}>
<h1 className={styles.title}>Edit Workout</h1>
<label className={styles.label}> Title:</label>
<input
type="text"
name="title"
value={workout.title}
onChange={handleOnChange}
className={styles.updateInput}
/>
<label className={styles.label}> Load (kg):</label>
<input
type="text"
name="loads"
value={workout.loads}
onChange={handleOnChange}
className={styles.updateInput}
/>
<label className={styles.label}> Reps:</label>
<input
type="text"
name="reps"
value={workout.reps}
onChange={handleOnChange}
className={styles.updateInput}
/>
<button onClick={updateWorkout} className={styles.updateButton}>
Update Workout
</button>
</div>
</div>
);
};
export default Edit;
首先,我们创建一个状态来存储将从我们的表中获取的锻炼卡详细信息。然后,我们使用钩子提取该id
卡的。useRouter
该getWorkout
函数调用 Supabase 客户端实例来过滤该id
锻炼卡并返回数据(标题、负荷和次数)。
返回锻炼卡详细信息后,我们可以创建updateWorkout
函数以使用该函数修改详细信息.update()
。一旦用户更新了锻炼并单击了更新锻炼按钮,就会发送一条警报消息,并且用户将被重定向回主页。
让我们看看它是如何工作的。
点击编辑图标进入编辑页面。我们将把标题从“Dumbell Press”重命名为“Arm Curl”:
要删除每张卡片上的锻炼,我们将创建handleDelete
将id
作为参数的函数。我们将调用 Supbase 实例以使用
.delete()
功能。这.eq('id', id)
指定id
要在表中删除的行。
const handleDelete = async (id) => {
try {
const user = supabase.auth.user();
const { data, error } = await supabase
.from("workouts")
.delete()
.eq("id", id)
.eq("user_id", user?.id);
fetchWorkouts();
alert("Workout deleted successfully");
} catch (error) {
alert(error.message);
}
};
用于检查正在删除的eq('user_id', user?.id)
卡是否属于该特定用户。该函数将被传递给文件中的WorkoutCard
组件index.js
并解构以供组件本身使用,如下所示:
const WorkoutCard = ({ data, handleDelete }) => {
return (
<div className={styles.workoutContainer}>
{data?.map((item) => (
<div key={item.id} className={styles.container}>
<p className={styles.title}>
{" "}
Title: {""}
{item.title}
</p>
<p className={styles.load}>
{" "}
Load(kg): {" "}
{item.loads}
</p>
<p className={styles.reps}>Reps:{item.reps}</p>
<p className={styles.time}>
created:{" "}
{formatDistanceToNow(new Date(item.inserted_at), {
addSuffix: true,
})}
</p>
<div className={styles.buttons}>
<Link href={`/edit/${item.id}`}>
<a className={styles.edit}>
<FiEdit />
</a>
</Link>
<button
onClick={() => handleDelete(item.id)}
className={styles.delete}
>
<BsTrash />
</button>
</div>
</div>
))}
</div>
);
};
成功删除卡后,将显示警报 Toast,并将用户重定向到主页。
现在,我们必须将我们的应用程序部署到 Vercel,以便 Internet 上的任何人都可以使用它!
要部署到 Vercel,您必须首先将代码推送到存储库,登录到 Vercel 仪表板,单击Create New Project,然后单击刚刚将代码推送到的存储库。
在Environment Variable字段中输入我们之前创建的环境变量及其值 (NEXT_PUBLIC_SUPABASE_URL
和NEXT_PUBLIC_SUPABASE_ANON_KEY
),然后单击Deploy以将您的应用程序部署到生产环境。
我们终于得到它了!
感谢您的阅读!我希望本教程为您提供使用 Next.js 和 Supabase 创建全栈应用程序所需的知识。
您可以根据用例自定义样式,因为本教程主要关注创建全栈应用程序的逻辑。
来源:https ://blog.logrocket.com/build-full-stack-app-next-js-supabase/
1660878300
次のフルスタック アプリケーションのフレームワークを構築して選択する場合、Next.js と Supabase を組み合わせることは、私の意見では最良の選択肢の 1 つです。
Supabase は、シームレスな認証など、多くの強力なツールを備えたオープン ソースの Firebase の代替手段です。開発者として、これはフルスタック アプリケーションを成功させるための鍵です。
認証に加えて、Supabase には、Postgres データベース、リアルタイム サブスクリプション、オブジェクト ストレージなどの他の機能が付属しています。Supabase は、開始または統合するのに最も簡単なサービスとしてのバックエンドの 1 つだと思います。
この記事では、Next.js と Supabase を使用してフルスタック アプリを構築する方法を学習します。Supabase プロジェクトをセットアップし、UI を構成し、認証と機能を実装する方法について説明します。
このアプリのコンセプトは、ユーザーが指定されたパラメーターに基づいてトレーニング活動を追跡および作成し、間違いや必要な変更があればこれらの活動を編集し、必要に応じて削除することです。始めましょう!
Next.js は、本番対応の React アプリケーションを構築するための最も簡単で最も一般的な方法の 1 つです。ここ数年、Next.js は急激な成長を遂げており、多くの企業がそれを採用してアプリケーションを構築しています。
Supabase は、PostgreSQL データベース上に構築された、Firebase に代わるサーバーレスのオープンソースの代替手段です。フルスタック アプリケーションの作成に必要なすべてのバックエンド サービスを提供します。
ユーザーは、テーブルやリレーションシップの作成から、PostgreSQL 上の SQL クエリやリアルタイム エンジンの作成に至るまで、Supabase インターフェイスからデータベースを管理できます。
Supabase には、フルスタック アプリケーションの開発をさらに容易にする非常に優れた機能が備わっています。これらの機能の一部は次のとおりです。
auth.users
テーブルを作成します。アプリケーションを作成すると、データベース内で参照できるアプリに登録するとすぐに、Supabase によってユーザーと ID も割り当てられます。ログイン方法については、メール、パスワード、マジック リンク、Google、GitHub など、さまざまな方法でユーザーを認証できます。Next.js テンプレートを使用してターミナルでプロジェクトを開始するには、次のコマンドを実行します。
npx create-next-app nextjs-supabase
nextjs-supabase
は、Next.js アプリ テンプレートを含むアプリのフォルダー名です。
後で Next.js アプリに接続するには、Supabase クライアント パッケージをインストールする必要があります。これを行うには、次のコマンドのいずれかを実行します。
yarn add @supabase/supabase-js
また
npm i @supabase/supabase-js
アプリのセットアップが完了したら、お気に入りのコード エディターでフォルダーを開きます。ここで、ファイル内の基本テンプレートを削除し、「ワークアウト アプリへようこそ」という見出しに/pages/index.js
置き換えることができます。h1
それが完了したら、ターミナルでコマンドを実行して、 http://localhost:3000yarn dev
でアプリを起動します。次のようなページが表示されます。
Supabase プロジェクトを設定するには、app.supabase.com にアクセスして、GitHub アカウントを使用してアプリ ダッシュボードにサインインします。
ログインしたら、組織を作成し、[すべてのプロジェクト] をクリックして組織内に新しいプロジェクトを設定できます。
[新しいプロジェクト]をクリックし、プロジェクトに名前とデータベース パスワードを指定します。[新しいプロジェクトを作成] ボタンをクリックします。プロジェクトが起動して実行されるまで数分かかります。
プロジェクトが作成されると、次のようなダッシュボードが表示されます。
このチュートリアルでは、という名前のプロジェクトを既に作成しています。workout-next-supabase.
それでは、ダッシュボードのSQL Editorアイコンをクリックし、 New Queryをクリックして、データベース テーブルを作成しましょう。以下の SQL クエリをエディタに入力し、[ RUN ] をクリックしてクエリを実行します。
CREATE TABLE workouts (
id bigint generated by default as identity primary key,
user_id uuid references auth.users not null,
user_email text,
title text,
loads text,
reps text,
inserted_at timestamp with time zone default timezone('utc'::text, now()) not null
);
alter table workouts enable row level security;
create policy "Individuals can create workouts." on workouts for
insert with check (auth.uid() = user_id);
create policy "Individuals can update their own workouts." on workouts for
update using (auth.uid() = user_id);
create policy "Individuals can delete their own workouts." on workouts for
delete using (auth.uid() = user_id);
create policy "Workouts are public." on workouts for
select using (true);
これにより、CRUD アプリケーションの構築に使用するワークアウト テーブルが作成されます。
テーブルの作成に加えて、行レベルのアクセス許可が有効になり、承認されたユーザーのみがワークアウトの詳細を作成、更新、または削除できるようになります。
ワークアウト テーブルがどのように表示されるかを確認するには、ダッシュボードの[テーブル エディター] アイコンをクリックして、作成したワークアウト テーブルを表示します。
このアプリケーションでは、7 つの列があります。
user_id
user_email
id
title
loads
reps
Date stamp
テーブルと列を設定したら、次のステップは、Supabase データベースを Next.js フロントエンド アプリケーションに接続することです。
Supabase を Next.js アプリに接続するには、Project URLとAnon Keyが必要です。これらは両方とも、データベース ダッシュボードで見つけることができます。これら 2 つのキーを取得するには、歯車アイコンをクリックして[設定]に移動し、[ API ] をクリックします。これら 2 つのキーが次のように表示されます。
もちろん、これらの値は機密情報であるため、ブラウザやリポジトリで公開したくありません。.env.local
私たちの利点として、Next.js は、プロジェクトのルートにファイルを作成できるようにする環境変数の組み込みサポートを提供します。これにより、環境変数が読み込まれ、接頭辞 を付けてブラウザーに公開されNEXT_PUBLIC
ます。
それでは、.env.local
プロジェクトのルートにファイルを作成し、URL とキーをファイルに含めましょう。
.env.local NEXT_PUBLIC_SUPABASE_URL= // プロジェクトの URL をここに貼り付けます NEXT_PUBLIC_SUPABASE_ANON_KEY= // スーパーベースの anon キーをここに貼り付けます
注意:デプロイ時にファイルが GitHub リポジトリにプッシュされないようにする (そして誰でも見ることができるようになる) のを防ぐために、ファイル
.env.local
に含めることを忘れないでください。gitignore
supabase.js
次に、プロジェクトのルートで呼び出されるファイルを作成して、Supabase クライアント ファイルを作成しましょう。ファイル内にsupabase.js
、次のコードを記述します。
// supabase.js
import { createClient } from "@supabase/supabase-js";
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
export const supabase = createClient(supabaseUrl, supabaseKey);
ここでは、createClient
Supabase から関数をインポートし、 という変数を作成していますsupabase
。関数を呼び出して、 URL ( ) と Anon Key ( )createClient
というパラメータを渡します。supabaseUrlsupabaseKey
これで、プロジェクトのどこでも Supabase クライアントを呼び出して使用できるようになりました。
まず、アプリを希望どおりに表示するように構成する必要があります。アプリが最初にロードされると、プロジェクト名とログインおよびサインアップオプションを含むナビゲーション バーが表示されます。ユーザーがサインアップしてログインすると、ナビゲーション バーに [ホーム] 、 [ログアウト] 、および[ワークアウトの作成]ボタンが表示されます。
また、ウェブサイトのすべてのページにフッターがあります。
これを行うには、およびファイルを格納するcomponent
フォルダーを作成します。次に、 内でコンポーネントをおよびコンポーネントでラップし、アプリのすべてのページに表示されるようにします。Navbar.jsFooter.js_app.jspagesNavbarFooter
// _app.js
import Footer from "../components/Footer";
import Navbar from "../components/Navbar";
import "../styles/globals.css";
function MyApp({ Component, pageProps }) {
return (
<div>
<Navbar/>
<Component {...pageProps}/>
<Footer />
</div>
);
}
export default MyApp;
私が使用したスタイルとこれら 2 つのコンポーネントがどのように見えるかを確認するために、ここに GitHub 要点を作成しました。
これで、ホームページは次のようになります。
ユーザー認証を実装するには、ファイル内のユーザー状態を初期化し、ユーザーをチェックして検証する関数を_app.js
作成します。validateUser
次に、返されたセッション オブジェクトにユーザー状態を設定します。
// _app.js
import { useState, useEffect } from "react";
import Footer from "../components/Footer";
import Navbar from "../components/Navbar";
import "../styles/globals.css";
import { supabase } from "../utils/supabase";
function MyApp({ Component, pageProps }) {
const [session, setSession] = useState(null);
useEffect(() => {
setSession(supabase.auth.session());
supabase.auth.onAuthStateChange((_event, session) => {
setSession(session);
});
}, []);
return (
<div>
<Navbar session={session} />
<Component {...pageProps} session={session} />
<Footer />
</div>
);
}
export default MyApp;
ユーザーがアプリのホームページを読み込んだときに、ログインまたはサインアップするように指示するボタンを表示したいと考えています。[ログイン] ボタンをクリックすると、ユーザーが電子メールとパスワードを入力できるページにユーザーをリダイレクトする必要があります。それらが既存のユーザーであり、ログインの詳細が有効である場合、それらはホームページにリダイレクトされます。
ユーザーが無効な認証情報を持っている場合、アラート メッセージが表示され、ユーザーに問題が通知されます。代わりにサインアップ オプションが表示されます。
ユーザーがサインアップすると、入力したメールアドレスに確認メールが送信されます。電子メールの本文にあるリンクをクリックして、電子メールを確認する必要があります。
ここで、[ログイン] ボタンをクリックすると、次のページのユーザー ページにリダイレクトされます。
これで、[サインアップ] ボタンをクリックして、電子メールを入力できます。
これをクリックすると、メールアドレスを確認するためのメールが送信されます。確認すると、ログインし、次のようなページが表示されます。
サインインしていない場合、アクティビティ ダッシュボードを表示したり、新しいワークアウトを作成するボタンを表示したり、ログアウトしたりできないことに注意してください。これは、最初に述べた、Supabase によって提供された認証です。
ここで、ワークアウトを作成、変更、および削除するユーザー機能の作成について詳しく説明します。
作成するすべてのワークアウトを取得して、ホームページにレンダリングする必要があります。index.js
ファイル内でこれを行います。
// /pages/index.js
import Head from "next/head";
import Link from "next/link";
import { useEffect, useState } from "react";
import styles from "../styles/Home.module.css";
import { supabase } from "../utils/supabase";
import WorkoutCard from "../components/WorkoutCard";
export default function Home({ session }) {
const [workouts, setWorkouts] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchWorkouts();
}, []);
const fetchWorkouts = async () => {
const user = supabase.auth.user();
try {
setLoading(true);
const { data, error } = await supabase
.from("workouts")
.select("*")
.eq("user_id", user?.id);
if (error) throw error;
setWorkouts(data);
} catch (error) {
alert(error.message);
} finally {
setLoading(false);
}
};
if (loading) {
return <div className={styles.loading}>Fetching Workouts...</div>;
}
return (
<div className={styles.container}>
<Head>
<title>Nextjs x Supabase</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<div className={styles.home}>
{!session?.user ? (
<div>
<p>
Welcome to Adrenargy. Kindly log in to your account or sign in for
a demo
</p>
</div>
) : (
<div>
<p className={styles.workoutHeading}>
Hello <span className={styles.email}>{session.user.email}</span>,
Welcome to your dashboard
</p>
{workouts?.length === 0 ? (
<div className={styles.noWorkout}>
<p>You have no workouts yet</p>
<Link href="/create">
<button className={styles.button}>
{" "}
Create a New Workout
</button>
</Link>
</div>
) : (
<div>
<p className={styles.workoutHeading}>Here are your workouts</p>
<WorkoutCard data={workouts}/>
</div>
)}
</div>
)}
</div>
</div>
);
}
このコンポーネントでは、ファイル内の propssession
から渡したオブジェクトを分解し、それを使用して承認されたユーザーを検証します。ユーザーがいない場合、ダッシュボードは表示されません。ユーザーがログインしている場合、ワークアウトのダッシュボードが表示されます。また、ワークアウトが作成されていない場合は、「まだワークアウトがありません」というテキストと、新しいワークアウトを作成するためのボタンが表示されます。page_app.js
workouts
作成したワークアウトをレンダリングするために、空の配列である とloading
、ブール値の を受け取る状態の2 つの状態がありtrue
ます。useEffect
ページがロードされたときにデータベースからワークアウト データを取得するために使用しています。
このfetchWorkouts
関数は、メソッドを使用してデータベース内のワークアウト テーブルからすべてのデータを返すために、Supabase インスタンスを呼び出すために使用されますselect
。。eq()
filter メソッドは、現在ログインしているユーザーと一致するユーザー ID を持つデータのみを除外して返すために使用されます。次に、setWorkouts
データベースから送信されたデータに設定され、データをフェッチするsetLoading
と元に戻されます。false
データがまだフェッチされている場合、ページには「Fetching Workouts…」と表示されます。データベースへのリクエストでワークアウトの配列が返された場合は、配列をマッピングしてWorkoutCard
コンポーネントをレンダリングします。
コンポーネントではWorkoutCard
、ワークアウトのタイトル、負荷、担当者、作成日時をレンダリングしています。作成された時刻は、ここで確認できるdate-fns
ライブラリを使用してフォーマットされています。次のセクションで、カードの作成を開始したときにカードがどのように見えるかを確認します。
// Workoutcard.js
import Link from "next/link";
import styles from "../styles/WorkoutCard.module.css";
import { BsTrash } from "react-icons/bs";
import { FiEdit } from "react-icons/fi";
import { formatDistanceToNow } from "date-fns/";
const WorkoutCard = ({ data }) => {
return (
<div className={styles.workoutContainer}>
{data?.map((item) => (
<div key={item.id} className={styles.container}>
<p className={styles.title}>
{" "}
Title: {""}
{item.title}
</p>
<p className={styles.load}>
{" "}
Load(kg): {" "}
{item.loads}
</p>
<p className={styles.reps}>Reps:{item.reps}</p>
<p className={styles.time}>
created:{" "}
{formatDistanceToNow(new Date(item.inserted_at), {
addSuffix: true,
})}
</p>
</div>
))}
</div>
);
};
export default WorkoutCard;
ログインしたので、ダッシュボードは新鮮できれいです。新しいワークアウトを作成する機能を実装するには、 andフォルダーにそれぞれcreate.js
とCreate.module.css
ファイルを追加し、いくつかのロジックとスタイリングを実装します。pagesstyles
// /pages/create.js
import { supabase } from "../utils/supabase";
import { useState } from "react";
import styles from "../styles/Create.module.css";
import { useRouter } from "next/router";
const Create = () => {
const initialState = {
title: "",
loads: "",
reps: "",
};
const router = useRouter();
const [workoutData, setWorkoutData] = useState(initialState);
const { title, loads, reps } = workoutData;
const handleChange = (e) => {
setWorkoutData({ ...workoutData, [e.target.name]: e.target.value });
};
const createWorkout = async () => {
try {
const user = supabase.auth.user();
const { data, error } = await supabase
.from("workouts")
.insert([
{
title,
loads,
reps,
user_id: user?.id,
},
])
.single();
if (error) throw error;
alert("Workout created successfully");
setWorkoutData(initialState);
router.push("/");
} catch (error) {
alert(error.message);
}
};
return (
<>
<div className={styles.container}>
<div className={styles.form}>
<p className={styles.title}>Create a New Workout</p>
<label className={styles.label}>Title:</label>
<input
type="text"
name="title"
value={title}
onChange={handleChange}
className={styles.input}
placeholder="Enter a title"
/>
<label className={styles.label}>Load (kg):</label>
<input
type="text"
name="loads"
value={loads}
onChange={handleChange}
className={styles.input}
placeholder="Enter weight load"
/>
<label className={styles.label}>Reps:</label>
<input
type="text"
name="reps"
value={reps}
onChange={handleChange}
className={styles.input}
placeholder="Enter number of reps"
/>
<button className={styles.button} onClick={createWorkout}>
Create Workout
</button>
</div>
</div>
</>
);
};
export default Create;
ここで、基本的な UI スコープは、新しいワークアウトを作成するためのフォームを持つことです。データベースの作成時に指定したように、フォームは 3 つのフィールド (タイトル、負荷、担当者) で構成されます。
状態に渡されたこれらすべてのフィールドを処理するために、初期状態オブジェクトが定義されますworkoutsData
。このonChange
関数は、入力フィールドの変更を処理するために使用されます。
このcreateWorkout
関数は、Supabase クライアント インスタンスを使用して、定義した初期状態フィールドを使用して新しいワークアウトを作成し、それをデータベース テーブルに挿入します。
最後に、新しいワークアウトが作成されたときに通知するアラート トーストがあります。
次に、ワークアウトが作成されたら、フォーム データを最初の空の文字列の状態に戻します。その後、router.push
メソッドを使用して、ユーザーをホームページに戻します。
ワークアウトを更新するには、ファイルを保持するフォルダーedit
内にという名前のフォルダーを作成します。ワークアウト コンポーネント カードに、このページにリンクする編集リンク アイコンを作成します。カードがホームページに表示されたら、この編集アイコンをクリックすると、その特定のカードの編集ページに移動します。pages[id].js
次に、必要なワークアウト カードの詳細をワークアウト テーブルから取得し、id
カードの承認された所有者によって取得します。次に、updateWorkout
ワークアウト カードの詳細を更新する関数を作成します。
// /pages/edit/[id].js
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import styles from "../../styles/Edit.module.css";
import { supabase } from "../../utils/supabase";
const Edit = () => {
const [workout, setWorkout] = useState("");
const router = useRouter();
const { id } = router.query;
useEffect(() => {
const user = supabase.auth.user();
const getWorkout = async () => {
const { data } = await supabase
.from("workouts")
.select("*")
.eq("user_id", user?.id)
.filter("id", "eq", id)
.single();
setWorkout(data);
};
getWorkout();
}, [id]);
const handleOnChange = (e) => {
setWorkout({
...workout,
[e.target.name]: e.target.value,
});
};
const { title, loads, reps } = workout;
const updateWorkout = async () => {
const user = supabase.auth.user();
const { data } = await supabase
.from("workouts")
.update({
title,
loads,
reps,
})
.eq("id", id)
.eq("user_id", user?.id);
alert("Workout updated successfully");
router.push("/");
};
return (
<div className={styles.container}>
<div className={styles.formContainer}>
<h1 className={styles.title}>Edit Workout</h1>
<label className={styles.label}> Title:</label>
<input
type="text"
name="title"
value={workout.title}
onChange={handleOnChange}
className={styles.updateInput}
/>
<label className={styles.label}> Load (kg):</label>
<input
type="text"
name="loads"
value={workout.loads}
onChange={handleOnChange}
className={styles.updateInput}
/>
<label className={styles.label}> Reps:</label>
<input
type="text"
name="reps"
value={workout.reps}
onChange={handleOnChange}
className={styles.updateInput}
/>
<button onClick={updateWorkout} className={styles.updateButton}>
Update Workout
</button>
</div>
</div>
);
};
export default Edit;
まず、テーブルからフェッチされるワークアウト カードの詳細を格納する状態を作成します。次に、フックid
を使用してそのカードのを抽出します。useRouter
このgetWorkout
関数は、Supabase クライアント インスタンスを呼び出してid
そのワークアウト カードをフィルター処理し、データ (タイトル、負荷、回数) を返します。
ワークアウト カードの詳細が返されたら、updateWorkout
関数を使用して詳細を変更する関数を作成できます.update()
。ユーザーがワークアウトを更新し、 [ワークアウトの更新] ボタンをクリックすると、警告メッセージが送信され、ユーザーはホームページにリダイレクトされます。
それがどのように機能するか見てみましょう。
編集アイコンをクリックして、編集ページに移動します。タイトルを「ダンベル プレス」から「アーム カール」に変更します。
各カードのワークアウトを削除するには、引数としてhandleDelete
を受け取る関数を作成します。id
Supabase インスタンスを呼び出して、ワークアウト カードを削除します。
.delete()
関数。テーブルで削除する行を.eq('id', id)
指定します。id
const handleDelete = async (id) => {
try {
const user = supabase.auth.user();
const { data, error } = await supabase
.from("workouts")
.delete()
.eq("id", id)
.eq("user_id", user?.id);
fetchWorkouts();
alert("Workout deleted successfully");
} catch (error) {
alert(error.message);
}
};
はeq('user_id', user?.id)
、削除されているカードがその特定のユーザーに属しているかどうかを確認するために使用されます。WorkoutCard
関数はファイル内のコンポーネントに渡され、index.js
次のようにコンポーネント自体で使用するために分解されます。
const WorkoutCard = ({ data, handleDelete }) => {
return (
<div className={styles.workoutContainer}>
{data?.map((item) => (
<div key={item.id} className={styles.container}>
<p className={styles.title}>
{" "}
Title: {""}
{item.title}
</p>
<p className={styles.load}>
{" "}
Load(kg): {" "}
{item.loads}
</p>
<p className={styles.reps}>Reps:{item.reps}</p>
<p className={styles.time}>
created:{" "}
{formatDistanceToNow(new Date(item.inserted_at), {
addSuffix: true,
})}
</p>
<div className={styles.buttons}>
<Link href={`/edit/${item.id}`}>
<a className={styles.edit}>
<FiEdit />
</a>
</Link>
<button
onClick={() => handleDelete(item.id)}
className={styles.delete}
>
<BsTrash />
</button>
</div>
</div>
))}
</div>
);
};
カードが正常に削除されるとアラート トーストが表示され、ユーザーはホームページにリダイレクトされます。
次に、アプリケーションを Vercel にデプロイして、インターネット上の誰もが使用できるようにする必要があります。
Vercel にデプロイするには、まずコードをリポジトリにプッシュし、Vercel ダッシュボードにログインして、[ Create New Project ]をクリックし、コードをプッシュしたばかりのリポジトリをクリックする必要があります。
前に作成した環境変数とその値 (NEXT_PUBLIC_SUPABASE_URL
およびNEXT_PUBLIC_SUPABASE_ANON_KEY
) を[環境変数] フィールドに入力し、[デプロイ] をクリックしてアプリを運用環境にデプロイします。
そして、ここにあります!
読んでくれてありがとう!このチュートリアルで、Next.js と Supabase を使用してフルスタック アプリケーションを作成するために必要な知識が得られることを願っています。
このチュートリアルは主にフルスタック アプリケーションを作成するロジックに焦点を当てているため、ユース ケースに合わせてスタイルをカスタマイズできます。
ソース: https://blog.logrocket.com/build-full-stack-app-next-js-supabase/
1660874700
Cuando se trata de crear y elegir marcos para su próxima aplicación de pila completa, combinar Next.js con Supabase es una de las mejores opciones para trabajar en mi opinión.
Supabase es una alternativa de Firebase de código abierto con muchas herramientas poderosas, incluida la autenticación sin problemas . Como desarrollador, esto es clave para crear una aplicación full-stack exitosa.
Además de la autenticación, Supabase viene con otras funciones, como una base de datos de Postgres, suscripciones en tiempo real y almacenamiento de objetos. Creo que Supabase es uno de los back-end como servicios más fáciles de comenzar o integrar.
En este artículo, aprenderemos a crear una aplicación de pila completa con Next.js y Supabase . Hablaremos sobre cómo configurar un proyecto de Supabase, configurar la interfaz de usuario e implementar la autenticación y las funcionalidades.
El concepto de esta aplicación es que los usuarios realicen un seguimiento y creen actividades de entrenamiento basadas en parámetros específicos, editen estas actividades si hay errores o cambios necesarios y las eliminen si es necesario. ¡Empecemos!
Next.js es una de las formas más fáciles y populares de crear aplicaciones React listas para producción. En los últimos años, Next.js ha experimentado un crecimiento exponencial significativo y muchas empresas lo han adoptado para crear sus aplicaciones.
Supabase es una alternativa de código abierto y sin servidor a Firebase construida sobre la base de datos PostgreSQL. Proporciona todos los servicios de back-end necesarios para crear una aplicación de pila completa.
Como usuario, puede administrar su base de datos desde la interfaz de Supabase, desde la creación de tablas y relaciones hasta la escritura de consultas SQL y el motor en tiempo real sobre PostgreSQL.
Supabase viene con funciones realmente geniales que hacen que el desarrollo de su aplicación de pila completa sea aún más fácil. Algunas de estas características son:
auth.users
tabla tan pronto como crea su base de datos. Cuando crea una aplicación, Supabase también asignará un usuario y una identificación tan pronto como se registre en la aplicación a la que se puede hacer referencia dentro de la base de datos. Para los métodos de inicio de sesión, hay diferentes formas de autenticar a los usuarios, como correo electrónico, contraseña, enlaces mágicos, Google, GitHub y más.Para iniciar nuestro proyecto en la terminal con la plantilla Next.js ejecutaremos el siguiente comando:
npx create-next-app nextjs-supabase
nextjs-supabase
es el nombre de la carpeta de nuestra aplicación donde incluiremos la plantilla de la aplicación Next.js.
Tendremos que instalar el paquete de cliente de Supabase para conectarnos a nuestra aplicación Next.js más adelante. Podemos hacerlo ejecutando cualquiera de los siguientes comandos:
yarn add @supabase/supabase-js
o
npm i @supabase/supabase-js
Una vez que la aplicación haya terminado de configurarse, abra la carpeta en su editor de código favorito. Ahora, podemos eliminar la plantilla básica de nuestro /pages/index.js
archivo y reemplazarla con un h1
encabezado que diga "Bienvenido a la aplicación Workout".
Una vez hecho esto, ejecute el comando yarn dev
en la terminal para iniciar su aplicación en http://localhost:3000 . Deberías ver una página como esta:
Para configurar un proyecto de Supabase, visite app.supabase.com para iniciar sesión en el panel de la aplicación con su cuenta de GitHub.
Una vez que inicie sesión, puede crear su organización y configurar un nuevo proyecto dentro de ella haciendo clic en Todos los proyectos .
Haga clic en Nuevo proyecto y asigne a su proyecto un nombre y una contraseña de base de datos. Haga clic en el botón Crear un nuevo proyecto ; su proyecto tardará un par de minutos en estar en funcionamiento.
Una vez que se haya creado el proyecto, debería ver un tablero como este:
Para este tutorial, ya creé un proyecto llamadoworkout-next-supabase.
Ahora, creemos nuestra tabla de base de datos haciendo clic en el ícono del Editor SQL en nuestro tablero y haciendo clic en Nueva consulta . Ingrese la consulta SQL a continuación en el editor y haga clic en EJECUTAR para ejecutar la consulta.
CREATE TABLE workouts (
id bigint generated by default as identity primary key,
user_id uuid references auth.users not null,
user_email text,
title text,
loads text,
reps text,
inserted_at timestamp with time zone default timezone('utc'::text, now()) not null
);
alter table workouts enable row level security;
create policy "Individuals can create workouts." on workouts for
insert with check (auth.uid() = user_id);
create policy "Individuals can update their own workouts." on workouts for
update using (auth.uid() = user_id);
create policy "Individuals can delete their own workouts." on workouts for
delete using (auth.uid() = user_id);
create policy "Workouts are public." on workouts for
select using (true);
Esto creará la tabla de entrenamiento que usaremos para construir nuestra aplicación CRUD.
Además de crear una tabla, se habilitarán permisos de nivel de fila para garantizar que solo los usuarios autorizados puedan crear, actualizar o eliminar los detalles de sus entrenamientos.
Para ver cómo se ve la tabla de ejercicios, podemos hacer clic en el ícono del Editor de tablas en el tablero para ver la tabla de ejercicios que acabamos de crear.
Para esta aplicación, tendremos siete columnas:
user_id
user_email
id
title
loads
reps
Date stamp
Una vez que nuestra tabla y columnas están configuradas, el siguiente paso es conectar nuestra base de datos Supabase con nuestra aplicación frontend Next.js.
Para conectar Supabase con nuestra aplicación Next.js, necesitaremos la URL de nuestro proyecto y Anon Key . Ambos se pueden encontrar en nuestro panel de base de datos. Para obtener estas dos claves, haga clic en el ícono de ajustes para ir a Configuración y luego haga clic en API . Verás que estas dos teclas aparecen así:
Por supuesto, no queremos exponer estos valores públicamente en el navegador o en nuestro repositorio, ya que se trata de información confidencial. Para nuestra ventaja, Next.js brinda soporte incorporado para variables de entorno que nos permiten crear un .env.local
archivo en la raíz de nuestro proyecto. Esto cargará nuestras variables de entorno y las expondrá al navegador con el prefijo NEXT_PUBLIC
.
Ahora, creemos un .env.local
archivo en la raíz de nuestro proyecto e incluyamos nuestra URL y claves en el archivo.
.env.local NEXT_PUBLIC_SUPABASE_URL= // pega aquí la url de tu proyecto NEXT_PUBLIC_SUPABASE_ANON_KEY= // pega aquí la clave anon de supabase
NB, no olvide incluirlo
.env.local
en sugitignore
archivo para evitar que se envíe al repositorio de GitHub (y esté disponible para que todos lo vean) al implementarlo.
Ahora vamos a crear nuestro archivo de cliente de Supabase creando un archivo llamado supabase.js
raíz de nuestro proyecto. Dentro del supabase.js
archivo, escribiremos el siguiente código:
// supabase.js
import { createClient } from "@supabase/supabase-js";
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
export const supabase = createClient(supabaseUrl, supabaseKey);
Aquí, estamos importando una createClient
función de Supabase y creando una variable llamada supabase
. Llamamos a la createClient
función y luego pasamos nuestros parámetros: URL ( supabaseUrl
) y Anon Key ( supabaseKey
).
¡Ahora podemos llamar y usar el cliente de Supabase en cualquier parte de nuestro proyecto!
Primero, necesitamos configurar nuestra aplicación para que se vea como queremos. Tendremos una barra de navegación con el nombre del proyecto y las opciones Iniciar sesión y Registrarse cuando la aplicación se cargue por primera vez. Cuando un usuario se registre e inicie sesión, mostraremos la barra de navegación para tener los botones Inicio , Cerrar sesión y Crear entrenamiento .
También habrá un pie de página en cada página del sitio web.
Para ello, crearemos una component
carpeta que albergará los archivos Navbar.js
y . Footer.js
Luego, dentro _app.js
de , envolveremos nuestro pages
componente con Navbar
y los Footer
componentes para que se muestren en todas las páginas de la aplicación.
// _app.js
import Footer from "../components/Footer";
import Navbar from "../components/Navbar";
import "../styles/globals.css";
function MyApp({ Component, pageProps }) {
return (
<div>
<Navbar/>
<Component {...pageProps}/>
<Footer />
</div>
);
}
export default MyApp;
Creé una esencia de GitHub aquí para ver cómo se ven estos dos componentes junto con los estilos que usé.
Ahora, nuestra página de inicio debería verse así:
Para implementar la autenticación de usuario, inicializaremos el estado del usuario en nuestro _app.js
archivo y crearemos una validateUser
función para verificar y validar un usuario. Luego estableceremos el estado de usuario en el objeto de sesión que se devuelve.
// _app.js
import { useState, useEffect } from "react";
import Footer from "../components/Footer";
import Navbar from "../components/Navbar";
import "../styles/globals.css";
import { supabase } from "../utils/supabase";
function MyApp({ Component, pageProps }) {
const [session, setSession] = useState(null);
useEffect(() => {
setSession(supabase.auth.session());
supabase.auth.onAuthStateChange((_event, session) => {
setSession(session);
});
}, []);
return (
<div>
<Navbar session={session} />
<Component {...pageProps} session={session} />
<Footer />
</div>
);
}
export default MyApp;
Cuando un usuario carga la página de inicio de nuestra aplicación, queremos mostrar un botón para indicarle que inicie sesión o se registre. Cuando se hace clic en el botón Iniciar sesión , debe redirigir al usuario a una página donde el usuario puede ingresar su correo electrónico y contraseña. Si es un usuario existente y los detalles de inicio de sesión son válidos, será redirigido a la página de inicio.
Si el usuario tiene credenciales no válidas, aparecerá un mensaje de alerta para informarle sobre el problema. En su lugar, se les mostrará una opción de registro.
Cuando el usuario se registre, se enviará un correo electrónico de confirmación al correo electrónico que ingresó. deberán confirmar su correo electrónico haciendo clic en el enlace en el cuerpo del correo electrónico.
Ahora, cuando hacemos clic en el botón Iniciar sesión , deberíamos ser redirigidos a la página del usuario a esta página:
Ahora, podemos hacer clic en el botón Registrarse e ingresar un correo electrónico.
Una vez que hagamos clic aquí, se enviará un correo electrónico para confirmar la dirección de correo electrónico. Al confirmar, nos iniciará sesión y deberíamos ver una página como esta:
Tenga en cuenta que si no hemos iniciado sesión, no podemos ver nuestro panel de actividad, ver un botón para crear un nuevo entrenamiento o cerrar sesión. ¡Esta fue la autenticación mencionada inicialmente que nos proporcionó Supabase!
Ahora, nos sumergiremos en la creación de la capacidad de un usuario para crear, modificar y eliminar sus entrenamientos.
Tendremos que buscar todos los entrenamientos que crearemos y mostrarlos en la página de inicio. Haremos esto dentro del index.js
archivo:
// /pages/index.js
import Head from "next/head";
import Link from "next/link";
import { useEffect, useState } from "react";
import styles from "../styles/Home.module.css";
import { supabase } from "../utils/supabase";
import WorkoutCard from "../components/WorkoutCard";
export default function Home({ session }) {
const [workouts, setWorkouts] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchWorkouts();
}, []);
const fetchWorkouts = async () => {
const user = supabase.auth.user();
try {
setLoading(true);
const { data, error } = await supabase
.from("workouts")
.select("*")
.eq("user_id", user?.id);
if (error) throw error;
setWorkouts(data);
} catch (error) {
alert(error.message);
} finally {
setLoading(false);
}
};
if (loading) {
return <div className={styles.loading}>Fetching Workouts...</div>;
}
return (
<div className={styles.container}>
<Head>
<title>Nextjs x Supabase</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<div className={styles.home}>
{!session?.user ? (
<div>
<p>
Welcome to Adrenargy. Kindly log in to your account or sign in for
a demo
</p>
</div>
) : (
<div>
<p className={styles.workoutHeading}>
Hello <span className={styles.email}>{session.user.email}</span>,
Welcome to your dashboard
</p>
{workouts?.length === 0 ? (
<div className={styles.noWorkout}>
<p>You have no workouts yet</p>
<Link href="/create">
<button className={styles.button}>
{" "}
Create a New Workout
</button>
</Link>
</div>
) : (
<div>
<p className={styles.workoutHeading}>Here are your workouts</p>
<WorkoutCard data={workouts}/>
</div>
)}
</div>
)}
</div>
</div>
);
}
En este componente, desestructuramos el session
objeto que pasamos de los page
accesorios en el _app.js
archivo y lo usamos para validar a los usuarios autorizados. Si no hay usuarios, el tablero no se mostrará. Si hay un usuario logueado, aparecerá el panel de entrenamientos. Y si no hay entrenamientos creados, aparecerá un texto que dice “Todavía no tienes ningún entrenamiento” y un botón para crear uno nuevo.
Para representar nuestros entrenamientos creados, tenemos dos estados: workouts
, una matriz vacía y un loading
estado que toma un valor booleano de true
. Estamos utilizando useEffect
para obtener los datos de los entrenamientos de la base de datos cuando se carga la página.
La fetchWorkouts
función se usa para llamar a la instancia de Supabase para devolver todos los datos de las tablas de entrenamiento en nuestra base de datos usando el select
método. Los . eq()
El método de filtro se usa para filtrar y devolver solo los datos con la identificación de usuario que coincide con el usuario conectado actual. Luego, setWorkouts
se establece en los datos enviados desde la base de datos y setLoading
se vuelve a establecer false
una vez que recuperamos nuestros datos.
Si aún se están obteniendo los datos, la página debería mostrar "Obteniendo entrenamientos..." y si la solicitud realizada a nuestra base de datos devuelve la matriz de nuestros entrenamientos, queremos mapear a través de la matriz y representar el WorkoutCard
componente.
En el WorkoutCard
componente, representamos el título del entrenamiento, la carga, las repeticiones y la fecha y hora en que se creó. El tiempo creado se está formateando usando la date-fns
biblioteca que puede consultar aquí . Veremos cómo se ven nuestras tarjetas cuando comencemos a crearlas en la siguiente sección.
// Workoutcard.js
import Link from "next/link";
import styles from "../styles/WorkoutCard.module.css";
import { BsTrash } from "react-icons/bs";
import { FiEdit } from "react-icons/fi";
import { formatDistanceToNow } from "date-fns/";
const WorkoutCard = ({ data }) => {
return (
<div className={styles.workoutContainer}>
{data?.map((item) => (
<div key={item.id} className={styles.container}>
<p className={styles.title}>
{" "}
Title: {""}
{item.title}
</p>
<p className={styles.load}>
{" "}
Load(kg): {" "}
{item.loads}
</p>
<p className={styles.reps}>Reps:{item.reps}</p>
<p className={styles.time}>
created:{" "}
{formatDistanceToNow(new Date(item.inserted_at), {
addSuffix: true,
})}
</p>
</div>
))}
</div>
);
};
export default WorkoutCard;
Ahora que hemos iniciado sesión, nuestro tablero está nuevo y limpio. Para implementar la capacidad de crear un nuevo entrenamiento, agregaremos create.js
y Create.module.css
archivos en la carpeta pages
y styles
respectivamente, e implementaremos algo de lógica y estilo.
// /pages/create.js
import { supabase } from "../utils/supabase";
import { useState } from "react";
import styles from "../styles/Create.module.css";
import { useRouter } from "next/router";
const Create = () => {
const initialState = {
title: "",
loads: "",
reps: "",
};
const router = useRouter();
const [workoutData, setWorkoutData] = useState(initialState);
const { title, loads, reps } = workoutData;
const handleChange = (e) => {
setWorkoutData({ ...workoutData, [e.target.name]: e.target.value });
};
const createWorkout = async () => {
try {
const user = supabase.auth.user();
const { data, error } = await supabase
.from("workouts")
.insert([
{
title,
loads,
reps,
user_id: user?.id,
},
])
.single();
if (error) throw error;
alert("Workout created successfully");
setWorkoutData(initialState);
router.push("/");
} catch (error) {
alert(error.message);
}
};
return (
<>
<div className={styles.container}>
<div className={styles.form}>
<p className={styles.title}>Create a New Workout</p>
<label className={styles.label}>Title:</label>
<input
type="text"
name="title"
value={title}
onChange={handleChange}
className={styles.input}
placeholder="Enter a title"
/>
<label className={styles.label}>Load (kg):</label>
<input
type="text"
name="loads"
value={loads}
onChange={handleChange}
className={styles.input}
placeholder="Enter weight load"
/>
<label className={styles.label}>Reps:</label>
<input
type="text"
name="reps"
value={reps}
onChange={handleChange}
className={styles.input}
placeholder="Enter number of reps"
/>
<button className={styles.button} onClick={createWorkout}>
Create Workout
</button>
</div>
</div>
</>
);
};
export default Create;
Aquí, el alcance básico de la interfaz de usuario es que tendremos un formulario para crear un nuevo entrenamiento. El formulario constará de tres campos (título, carga y representantes) como especificamos al crear nuestra base de datos.
Se define un objeto de estado inicial para manejar todos estos campos que se pasaron al workoutsData
estado. La onChange
función se utiliza para manejar los cambios del campo de entrada.
La createWorkout
función usa la instancia del cliente Supabase para crear un nuevo entrenamiento usando los campos de estado inicial que definimos e insertarlo en la tabla de la base de datos.
Por último, tenemos un brindis de alerta que nos informa cuando se ha creado nuestro nuevo entrenamiento.
Luego, volvemos a establecer los datos del formulario en el estado inicial de cadena vacía una vez que se ha creado nuestro entrenamiento. Después de eso, estamos usando el router.push
método para que el usuario regrese a la página de inicio.
Para actualizar un entrenamiento, crearemos una carpeta llamada edit
dentro de nuestra pages
carpeta que contendrá nuestro [id].js
archivo. Crearemos un icono de enlace de edición en nuestra tarjeta de componente de entrenamiento que enlaza con esta página. Cuando las tarjetas se representan en la página de inicio, podemos hacer clic en este icono de edición y nos llevará a la página de edición de esa tarjeta en particular.
Luego buscaremos los detalles de la tarjeta de entrenamiento necesaria para ser actualizados desde nuestra tabla de entrenamientos por él id
y el propietario autorizado de la tarjeta. Luego, crearemos una updateWorkout
función para actualizar los detalles de nuestra tarjeta de entrenamiento:
// /pages/edit/[id].js
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import styles from "../../styles/Edit.module.css";
import { supabase } from "../../utils/supabase";
const Edit = () => {
const [workout, setWorkout] = useState("");
const router = useRouter();
const { id } = router.query;
useEffect(() => {
const user = supabase.auth.user();
const getWorkout = async () => {
const { data } = await supabase
.from("workouts")
.select("*")
.eq("user_id", user?.id)
.filter("id", "eq", id)
.single();
setWorkout(data);
};
getWorkout();
}, [id]);
const handleOnChange = (e) => {
setWorkout({
...workout,
[e.target.name]: e.target.value,
});
};
const { title, loads, reps } = workout;
const updateWorkout = async () => {
const user = supabase.auth.user();
const { data } = await supabase
.from("workouts")
.update({
title,
loads,
reps,
})
.eq("id", id)
.eq("user_id", user?.id);
alert("Workout updated successfully");
router.push("/");
};
return (
<div className={styles.container}>
<div className={styles.formContainer}>
<h1 className={styles.title}>Edit Workout</h1>
<label className={styles.label}> Title:</label>
<input
type="text"
name="title"
value={workout.title}
onChange={handleOnChange}
className={styles.updateInput}
/>
<label className={styles.label}> Load (kg):</label>
<input
type="text"
name="loads"
value={workout.loads}
onChange={handleOnChange}
className={styles.updateInput}
/>
<label className={styles.label}> Reps:</label>
<input
type="text"
name="reps"
value={workout.reps}
onChange={handleOnChange}
className={styles.updateInput}
/>
<button onClick={updateWorkout} className={styles.updateButton}>
Update Workout
</button>
</div>
</div>
);
};
export default Edit;
Primero, creamos un estado para almacenar los detalles de la tarjeta de entrenamiento que se obtendrán de nuestra tabla. Luego, extraemos el id
de esa tarjeta usando el useRouter
gancho. La getWorkout
función llama a la instancia del cliente de Supabase para filtrar la id
de esa tarjeta de entrenamiento y devuelve los datos (título, cargas y repeticiones).
Una vez que se han devuelto los detalles de la tarjeta de entrenamiento, podemos crear nuestra updateWorkout
función para modificar los detalles usando la .update()
función. Una vez que el usuario ha actualizado el entrenamiento y se hace clic en el botón Actualizar entrenamiento , se envía un mensaje de alerta y el usuario será redirigido a la página de inicio.
Vamos a ver cómo funciona.
Haga clic en el icono de edición para ir a la página de edición. Cambiaremos el nombre del título de "Prensa con mancuernas" a "Doblar brazos":
Para eliminar un entrenamiento en cada tarjeta, crearemos la handleDelete
función que tomará id
como argumento. Llamaremos a la instancia de Supabase para eliminar una tarjeta de entrenamiento usando el
.delete()
función. Esto .eq('id', id)
especifica el valor id
de la fila que se eliminará en la tabla.
const handleDelete = async (id) => {
try {
const user = supabase.auth.user();
const { data, error } = await supabase
.from("workouts")
.delete()
.eq("id", id)
.eq("user_id", user?.id);
fetchWorkouts();
alert("Workout deleted successfully");
} catch (error) {
alert(error.message);
}
};
El eq('user_id', user?.id)
se utiliza para verificar si la tarjeta que se está eliminando pertenece a ese usuario en particular. La función se pasará al WorkoutCard
componente en el index.js
archivo y se desestructurará para su uso en el propio componente de la siguiente manera:
const WorkoutCard = ({ data, handleDelete }) => {
return (
<div className={styles.workoutContainer}>
{data?.map((item) => (
<div key={item.id} className={styles.container}>
<p className={styles.title}>
{" "}
Title: {""}
{item.title}
</p>
<p className={styles.load}>
{" "}
Load(kg): {" "}
{item.loads}
</p>
<p className={styles.reps}>Reps:{item.reps}</p>
<p className={styles.time}>
created:{" "}
{formatDistanceToNow(new Date(item.inserted_at), {
addSuffix: true,
})}
</p>
<div className={styles.buttons}>
<Link href={`/edit/${item.id}`}>
<a className={styles.edit}>
<FiEdit />
</a>
</Link>
<button
onClick={() => handleDelete(item.id)}
className={styles.delete}
>
<BsTrash />
</button>
</div>
</div>
))}
</div>
);
};
Se mostrará un brindis de alerta una vez que la tarjeta se haya eliminado con éxito y el usuario será redirigido a la página de inicio.
¡Ahora, tenemos que implementar nuestra aplicación en Vercel para que cualquiera en Internet pueda usarla!
Para implementar en Vercel, primero debe enviar su código a su repositorio, iniciar sesión en su panel de control de Vercel, hacer clic en Crear nuevo proyecto y hacer clic en el repositorio al que acaba de enviar su código.
Ingrese las variables de entorno que creamos anteriormente junto con sus valores ( NEXT_PUBLIC_SUPABASE_URL
y NEXT_PUBLIC_SUPABASE_ANON_KEY
) en el campo Variable de entorno y haga clic en Implementar para implementar su aplicación en producción.
¡Y ahí lo tenemos!
¡Gracias por leer! Espero que este tutorial le brinde los conocimientos necesarios para crear una aplicación de pila completa con Next.js y Supabase.
Puede personalizar el estilo para su caso de uso, ya que este tutorial se centra principalmente en la lógica de crear una aplicación de pila completa.
Fuente: https://blog.logrocket.com/build-full-stack-app-next-js-supabase/
1660874400
Quando se trata de construir e escolher frameworks para seu próximo aplicativo full-stack, combinar Next.js com Supabase é uma das melhores opções para trabalhar na minha opinião.
O Supabase é uma alternativa de código aberto ao Firebase com muitas ferramentas poderosas, incluindo autenticação perfeita . Como desenvolvedor, isso é fundamental para criar um aplicativo full-stack bem-sucedido.
Além da autenticação, o Supabase vem com outros recursos, como banco de dados Postgres, assinaturas em tempo real e armazenamento de objetos. Acredito que o Supabase é um dos serviços de back-end como um serviço mais fáceis de começar ou integrar.
Neste artigo, aprenderemos como criar um aplicativo full-stack usando Next.js e Supabase . Falaremos sobre como configurar um projeto Supabase, configurar a interface do usuário e implementar autenticação e funcionalidades.
O conceito deste aplicativo é que os usuários rastreiem e criem atividades de treino com base em parâmetros especificados, editem essas atividades se houver erros ou alterações necessárias e as excluam, se necessário. Vamos começar!
Next.js é uma das maneiras mais fáceis e populares de construir aplicativos React prontos para produção. Nos últimos anos, o Next.js experimentou um crescimento exponencial significativo e muitas empresas o adotaram para criar seus aplicativos.
O Supabase é uma alternativa de código aberto e sem servidor ao Firebase , construída sobre o banco de dados PostgreSQL. Ele fornece todos os serviços de back-end necessários para criar um aplicativo full-stack.
Como usuário, você pode gerenciar seu banco de dados a partir da interface do Supabase, desde a criação de tabelas e relacionamentos até a gravação de suas consultas SQL e mecanismo em tempo real no PostgreSQL.
O Supabase vem com recursos muito legais que facilitam ainda mais o desenvolvimento de aplicativos full-stack. Algumas dessas características são:
auth.users
tabela assim que você cria seu banco de dados. Ao criar um aplicativo, o Supabase também atribuirá um usuário e ID assim que você se registrar no aplicativo que pode ser referenciado no banco de dados. Para métodos de login, existem diferentes maneiras de autenticar usuários, como e-mail, senha, links mágicos, Google, GitHub e muito maisPara iniciar nosso projeto no terminal com o template Next.js, executaremos o seguinte comando:
npx create-next-app nextjs-supabase
nextjs-supabase
é o nome da pasta do nosso aplicativo onde englobaremos o modelo de aplicativo Next.js.
Precisaremos instalar o pacote do cliente Supabase para nos conectarmos ao nosso aplicativo Next.js posteriormente. Podemos fazer isso executando um dos seguintes comandos:
yarn add @supabase/supabase-js
ou
npm i @supabase/supabase-js
Depois que o aplicativo terminar de configurar, abra a pasta em seu editor de código favorito. Agora, podemos remover o modelo básico em nosso /pages/index.js
arquivo e substituí-lo por um h1
título dizendo “Bem-vindo ao aplicativo de treino”.
Feito isso, execute o comando yarn dev
no terminal para iniciar seu aplicativo em http://localhost:3000 . Você deve ver uma página como esta:
Para configurar um projeto Supabase, visite app.supabase.com para entrar no painel do aplicativo usando sua conta do GitHub.
Depois de fazer login, você pode criar sua organização e configurar um novo projeto dentro dela clicando em Todos os projetos .
Clique em New Project e dê ao seu projeto um nome e uma senha de banco de dados. Clique no botão Criar um novo projeto ; levará alguns minutos para que seu projeto esteja funcionando.
Depois que o projeto for criado, você deverá ver um painel como este:
Para este tutorial, já criei um projeto chamadoworkout-next-supabase.
Agora, vamos criar nossa tabela de banco de dados clicando no ícone SQL Editor em nosso painel e clicando em New Query . Insira a consulta SQL abaixo no editor e clique em EXECUTAR para executar a consulta.
CREATE TABLE workouts (
id bigint generated by default as identity primary key,
user_id uuid references auth.users not null,
user_email text,
title text,
loads text,
reps text,
inserted_at timestamp with time zone default timezone('utc'::text, now()) not null
);
alter table workouts enable row level security;
create policy "Individuals can create workouts." on workouts for
insert with check (auth.uid() = user_id);
create policy "Individuals can update their own workouts." on workouts for
update using (auth.uid() = user_id);
create policy "Individuals can delete their own workouts." on workouts for
delete using (auth.uid() = user_id);
create policy "Workouts are public." on workouts for
select using (true);
Isso criará a tabela de exercícios que usaremos para construir nosso aplicativo CRUD.
Além de criar uma tabela, as permissões em nível de linha serão habilitadas para garantir que apenas usuários autorizados possam criar, atualizar ou excluir os detalhes de seus treinos.
Para conferir a aparência da tabela de exercícios, podemos clicar no ícone Editor de Tabelas no painel para ver a tabela de exercícios que acabamos de criar.
Para esta aplicação, teremos sete colunas:
user_id
user_email
id
title
loads
reps
Date stamp
Uma vez que nossa tabela e colunas estejam definidas, o próximo passo é conectar nosso banco de dados Supabase com nosso aplicativo frontend Next.js!
Para conectar o Supabase ao nosso aplicativo Next.js, precisaremos do URL do projeto e da chave Anon . Ambos podem ser encontrados em nosso painel de banco de dados. Para obter essas duas chaves, clique no ícone de engrenagem para acessar Configurações e clique em API . Você verá essas duas chaves aparecerem assim:
Obviamente, não queremos expor esses valores publicamente no navegador ou em nosso repositório, pois são informações confidenciais. Para nossa vantagem, o Next.js fornece suporte embutido para variáveis de ambiente que nos permitem criar um .env.local
arquivo na raiz do nosso projeto. Isso carregará nossas variáveis de ambiente e as exporá ao navegador prefixando-as com NEXT_PUBLIC
.
Agora, vamos criar um .env.local
arquivo na raiz do nosso projeto e incluir nossa URL e chaves no arquivo.
.env.local NEXT_PUBLIC_SUPABASE_URL= // cole a url do seu projeto aqui NEXT_PUBLIC_SUPABASE_ANON_KEY= // cole sua chave anon do supabase aqui
NB, não se esqueça de incluir
.env.local
em seugitignore
arquivo para evitar que ele seja enviado para o repositório do GitHub (e disponível para todos verem) durante a implantação.
Agora vamos criar nosso arquivo cliente Supabase criando um arquivo chamado supabase.js
na raiz do nosso projeto. Dentro do supabase.js
arquivo, escreveremos o seguinte código:
// supabase.js
import { createClient } from "@supabase/supabase-js";
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
export const supabase = createClient(supabaseUrl, supabaseKey);
Aqui, estamos importando uma createClient
função do Supabase e criando uma variável chamada supabase
. Chamamos a createClient
função e passamos nossos parâmetros: URL ( supabaseUrl
) e Anon Key ( supabaseKey
).
Agora, podemos chamar e usar o cliente Supabase em qualquer lugar do nosso projeto!
Primeiro, precisamos configurar nosso aplicativo para ter a aparência que queremos. Teremos uma barra de navegação com o nome do projeto e as opções de Login e Inscrição quando o aplicativo for carregado pela primeira vez. Quando um usuário se inscrever e efetuar login, exibiremos a barra de navegação com os botões Home , Logout e Create Workout .
Haverá também um rodapé em todas as páginas do site.
Para fazer isso, criaremos uma component
pasta que abrigará os arquivos Navbar.js
e . Footer.js
Em seguida, dentro _app.js
de , envolveremos nosso pages
componente com o Navbar
e os Footer
componentes para que sejam exibidos em todas as páginas do aplicativo.
// _app.js
import Footer from "../components/Footer";
import Navbar from "../components/Navbar";
import "../styles/globals.css";
function MyApp({ Component, pageProps }) {
return (
<div>
<Navbar/>
<Component {...pageProps}/>
<Footer />
</div>
);
}
export default MyApp;
Eu criei um GitHub gist aqui para ver como esses dois componentes se parecem com os estilos que usei.
Agora, nossa página inicial deve ficar assim:
Para implementar a autenticação do usuário, inicializaremos o estado do usuário em nosso _app.js
arquivo e criaremos uma validateUser
função para verificar e validar um usuário. Em seguida, definiremos o estado do usuário para o objeto de sessão retornado.
// _app.js
import { useState, useEffect } from "react";
import Footer from "../components/Footer";
import Navbar from "../components/Navbar";
import "../styles/globals.css";
import { supabase } from "../utils/supabase";
function MyApp({ Component, pageProps }) {
const [session, setSession] = useState(null);
useEffect(() => {
setSession(supabase.auth.session());
supabase.auth.onAuthStateChange((_event, session) => {
setSession(session);
});
}, []);
return (
<div>
<Navbar session={session} />
<Component {...pageProps} session={session} />
<Footer />
</div>
);
}
export default MyApp;
Quando um usuário carrega a página inicial do nosso aplicativo, queremos exibir um botão para instruí-lo a fazer login ou se inscrever. Ao clicar no botão Login , ele deve redirecionar o usuário para uma página onde o usuário pode inserir seu e-mail e senha. Se for um usuário existente e os detalhes de login forem válidos, eles serão redirecionados para a página inicial.
Se o usuário tiver credenciais inválidas, uma mensagem de alerta será exibida para informar o usuário sobre o problema. Em vez disso, eles verão uma opção de inscrição.
Quando o usuário se inscrever, um e-mail de confirmação será enviado para o e-mail inserido. eles precisarão confirmar o e-mail clicando no link no corpo do e-mail.
Agora, quando clicamos no botão Login , devemos ser redirecionados para a página do usuário para esta página:
Agora, podemos clicar no botão Sign up e inserir um e-mail.
Assim que clicarmos, um e-mail será enviado para confirmar o endereço de e-mail. Ao confirmar, ele fará o login e devemos ver uma página como esta:
Observe que, se não tivermos feito login, não poderemos ver nosso painel de atividades, ver um botão para criar um novo treino ou sair. Esta foi a autenticação mencionada inicialmente que nos foi fornecida pela Supabase!
Agora, vamos nos aprofundar na criação da capacidade do usuário de criar, modificar e excluir seus treinos.
Precisaremos buscar todos os treinos que vamos criar e renderizá-los na página inicial. Faremos isso dentro do index.js
arquivo:
// /pages/index.js
import Head from "next/head";
import Link from "next/link";
import { useEffect, useState } from "react";
import styles from "../styles/Home.module.css";
import { supabase } from "../utils/supabase";
import WorkoutCard from "../components/WorkoutCard";
export default function Home({ session }) {
const [workouts, setWorkouts] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchWorkouts();
}, []);
const fetchWorkouts = async () => {
const user = supabase.auth.user();
try {
setLoading(true);
const { data, error } = await supabase
.from("workouts")
.select("*")
.eq("user_id", user?.id);
if (error) throw error;
setWorkouts(data);
} catch (error) {
alert(error.message);
} finally {
setLoading(false);
}
};
if (loading) {
return <div className={styles.loading}>Fetching Workouts...</div>;
}
return (
<div className={styles.container}>
<Head>
<title>Nextjs x Supabase</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<div className={styles.home}>
{!session?.user ? (
<div>
<p>
Welcome to Adrenargy. Kindly log in to your account or sign in for
a demo
</p>
</div>
) : (
<div>
<p className={styles.workoutHeading}>
Hello <span className={styles.email}>{session.user.email}</span>,
Welcome to your dashboard
</p>
{workouts?.length === 0 ? (
<div className={styles.noWorkout}>
<p>You have no workouts yet</p>
<Link href="/create">
<button className={styles.button}>
{" "}
Create a New Workout
</button>
</Link>
</div>
) : (
<div>
<p className={styles.workoutHeading}>Here are your workouts</p>
<WorkoutCard data={workouts}/>
</div>
)}
</div>
)}
</div>
</div>
);
}
Neste componente, estamos desestruturando o session
objeto que passamos das page
props no _app.js
arquivo e usando isso para validar usuários autorizados. Se não houver usuários, o painel não será exibido. Se houver um usuário logado, o painel de treinos aparecerá. E se não houver treinos criados, aparecerá um texto dizendo “Você não tem treino ainda” e um botão para criar um novo.
Para renderizar nossos treinos criados, temos dois estados: workouts
, um array vazio e um loading
estado que recebe um valor booleano de true
. Estamos usando useEffect
para buscar os dados de treinos do banco de dados quando a página é carregada.
A fetchWorkouts
função é usada para chamar a instância do Supabase para retornar todos os dados das tabelas de treino em nosso banco de dados usando o select
método. O . eq()
O método filter é usado para filtrar e retornar apenas os dados com o ID do usuário correspondente ao usuário conectado no momento. Em seguida, setWorkouts
é definido para os dados enviados do banco de dados e setLoading
é definido de volta false
quando buscamos nossos dados.
Se os dados ainda estiverem sendo buscados, a página deverá exibir “Fetching Workouts…” e se a requisição feita ao nosso banco de dados retornar o array dos nossos treinos, queremos mapear pelo array e renderizar o WorkoutCard
componente.
No WorkoutCard
componente, estamos renderizando o título do treino, carga, repetições e a data e hora em que foi criado. A hora criada está sendo formatada usando a date-fns
biblioteca que você pode conferir aqui . Veremos como nossos cartões ficarão quando começarmos a criá-los na próxima seção.
// Workoutcard.js
import Link from "next/link";
import styles from "../styles/WorkoutCard.module.css";
import { BsTrash } from "react-icons/bs";
import { FiEdit } from "react-icons/fi";
import { formatDistanceToNow } from "date-fns/";
const WorkoutCard = ({ data }) => {
return (
<div className={styles.workoutContainer}>
{data?.map((item) => (
<div key={item.id} className={styles.container}>
<p className={styles.title}>
{" "}
Title: {""}
{item.title}
</p>
<p className={styles.load}>
{" "}
Load(kg): {" "}
{item.loads}
</p>
<p className={styles.reps}>Reps:{item.reps}</p>
<p className={styles.time}>
created:{" "}
{formatDistanceToNow(new Date(item.inserted_at), {
addSuffix: true,
})}
</p>
</div>
))}
</div>
);
};
export default WorkoutCard;
Agora que efetuamos login, nosso painel está atualizado e limpo. Para implementar a capacidade de criar um novo treino, adicionaremos create.js
e Create.module.css
arquivos na pasta pages
e styles
respectivamente, e implementaremos alguma lógica e estilo.
// /pages/create.js
import { supabase } from "../utils/supabase";
import { useState } from "react";
import styles from "../styles/Create.module.css";
import { useRouter } from "next/router";
const Create = () => {
const initialState = {
title: "",
loads: "",
reps: "",
};
const router = useRouter();
const [workoutData, setWorkoutData] = useState(initialState);
const { title, loads, reps } = workoutData;
const handleChange = (e) => {
setWorkoutData({ ...workoutData, [e.target.name]: e.target.value });
};
const createWorkout = async () => {
try {
const user = supabase.auth.user();
const { data, error } = await supabase
.from("workouts")
.insert([
{
title,
loads,
reps,
user_id: user?.id,
},
])
.single();
if (error) throw error;
alert("Workout created successfully");
setWorkoutData(initialState);
router.push("/");
} catch (error) {
alert(error.message);
}
};
return (
<>
<div className={styles.container}>
<div className={styles.form}>
<p className={styles.title}>Create a New Workout</p>
<label className={styles.label}>Title:</label>
<input
type="text"
name="title"
value={title}
onChange={handleChange}
className={styles.input}
placeholder="Enter a title"
/>
<label className={styles.label}>Load (kg):</label>
<input
type="text"
name="loads"
value={loads}
onChange={handleChange}
className={styles.input}
placeholder="Enter weight load"
/>
<label className={styles.label}>Reps:</label>
<input
type="text"
name="reps"
value={reps}
onChange={handleChange}
className={styles.input}
placeholder="Enter number of reps"
/>
<button className={styles.button} onClick={createWorkout}>
Create Workout
</button>
</div>
</div>
</>
);
};
export default Create;
Aqui, o escopo básico da interface do usuário é que teremos um formulário para criar um novo treino. O formulário consistirá em três campos (título, carga e representantes) conforme especificamos ao criar nosso banco de dados.
Um objeto de estado inicial é definido para lidar com todos esses campos que foram passados para o workoutsData
estado. A onChange
função é usada para lidar com as alterações do campo de entrada.
A createWorkout
função usa a instância do cliente Supabase para criar um novo treino usando os campos de estado inicial que definimos e inseri-lo na tabela do banco de dados.
Por fim, temos um brinde de alerta que nos informa quando nosso novo treino foi criado.
Em seguida, definimos os dados do formulário de volta ao estado inicial da string vazia depois que nosso treino foi criado. Depois disso, estamos usando o router.push
método para levar o usuário de volta à página inicial.
Para atualizar um treino, criaremos uma pasta chamada edit
dentro de nossa pages
pasta que conterá nosso [id].js
arquivo. Criaremos um ícone de link de edição em nosso cartão de componente de treino com link para esta página. Quando os cartões são renderizados na página inicial, podemos clicar neste ícone de edição e isso nos levará à página de edição desse cartão em particular.
Em seguida, buscaremos os detalhes do cartão de treino necessário para serem atualizados em nossa tabela de treinos por ele id
e pelo proprietário autorizado do cartão. Em seguida, criaremos uma updateWorkout
função para atualizar os detalhes do cartão de treino:
// /pages/edit/[id].js
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import styles from "../../styles/Edit.module.css";
import { supabase } from "../../utils/supabase";
const Edit = () => {
const [workout, setWorkout] = useState("");
const router = useRouter();
const { id } = router.query;
useEffect(() => {
const user = supabase.auth.user();
const getWorkout = async () => {
const { data } = await supabase
.from("workouts")
.select("*")
.eq("user_id", user?.id)
.filter("id", "eq", id)
.single();
setWorkout(data);
};
getWorkout();
}, [id]);
const handleOnChange = (e) => {
setWorkout({
...workout,
[e.target.name]: e.target.value,
});
};
const { title, loads, reps } = workout;
const updateWorkout = async () => {
const user = supabase.auth.user();
const { data } = await supabase
.from("workouts")
.update({
title,
loads,
reps,
})
.eq("id", id)
.eq("user_id", user?.id);
alert("Workout updated successfully");
router.push("/");
};
return (
<div className={styles.container}>
<div className={styles.formContainer}>
<h1 className={styles.title}>Edit Workout</h1>
<label className={styles.label}> Title:</label>
<input
type="text"
name="title"
value={workout.title}
onChange={handleOnChange}
className={styles.updateInput}
/>
<label className={styles.label}> Load (kg):</label>
<input
type="text"
name="loads"
value={workout.loads}
onChange={handleOnChange}
className={styles.updateInput}
/>
<label className={styles.label}> Reps:</label>
<input
type="text"
name="reps"
value={workout.reps}
onChange={handleOnChange}
className={styles.updateInput}
/>
<button onClick={updateWorkout} className={styles.updateButton}>
Update Workout
</button>
</div>
</div>
);
};
export default Edit;
Primeiro, criamos um estado para armazenar os detalhes do cartão de treino que serão buscados em nossa tabela. Em seguida, extraímos o id
desse cartão usando o useRouter
gancho. A getWorkout
função chama a instância do cliente Supabase para filtrar o id
cartão de treino e retorna os dados (título, cargas e representantes).
Depois que os detalhes do cartão de treino forem retornados, podemos criar nossa updateWorkout
função para modificar os detalhes usando a .update()
função. Assim que o treino for atualizado pelo usuário e o botão Atualizar treino for clicado, uma mensagem de alerta será enviada e o usuário será redirecionado para a página inicial.
Vamos ver como isso funciona.
Clique no ícone de edição para ir para a página de edição. Vamos renomear o título de “Dumbell Press” para “Arm Curl”:
Para excluir um treino em cada cartão, criaremos a handleDelete
função que receberá o id
como argumento. Chamaremos a instância do Supabase para excluir um cartão de treino usando o
.delete()
função. Isso .eq('id', id)
especifica a id
linha a ser excluída na tabela.
const handleDelete = async (id) => {
try {
const user = supabase.auth.user();
const { data, error } = await supabase
.from("workouts")
.delete()
.eq("id", id)
.eq("user_id", user?.id);
fetchWorkouts();
alert("Workout deleted successfully");
} catch (error) {
alert(error.message);
}
};
O eq('user_id', user?.id)
é usado para verificar se o cartão que está sendo excluído pertence a esse usuário específico. A função será passada para o WorkoutCard
componente no index.js
arquivo e desestruturada para uso no próprio componente da seguinte forma:
const WorkoutCard = ({ data, handleDelete }) => {
return (
<div className={styles.workoutContainer}>
{data?.map((item) => (
<div key={item.id} className={styles.container}>
<p className={styles.title}>
{" "}
Title: {""}
{item.title}
</p>
<p className={styles.load}>
{" "}
Load(kg): {" "}
{item.loads}
</p>
<p className={styles.reps}>Reps:{item.reps}</p>
<p className={styles.time}>
created:{" "}
{formatDistanceToNow(new Date(item.inserted_at), {
addSuffix: true,
})}
</p>
<div className={styles.buttons}>
<Link href={`/edit/${item.id}`}>
<a className={styles.edit}>
<FiEdit />
</a>
</Link>
<button
onClick={() => handleDelete(item.id)}
className={styles.delete}
>
<BsTrash />
</button>
</div>
</div>
))}
</div>
);
};
Um brinde de alerta será exibido assim que o cartão for excluído com sucesso e o usuário será redirecionado para a página inicial.
Agora, temos que implantar nosso aplicativo no Vercel para que qualquer pessoa na Internet possa usá-lo!
Para implantar no Vercel, você deve primeiro enviar seu código para seu repositório, fazer login no painel do Vercel, clicar em Criar novo projeto e clicar no repositório para o qual você acabou de enviar seu código.
Insira as variáveis de ambiente que criamos anteriormente junto com seus valores ( NEXT_PUBLIC_SUPABASE_URL
e NEXT_PUBLIC_SUPABASE_ANON_KEY
) no campo Variável de ambiente e clique em Implantar para implantar seu aplicativo na produção.
E aí temos que!
Obrigado por ler! Espero que este tutorial forneça o conhecimento necessário para criar um aplicativo full-stack usando Next.js e Supabase.
Você pode personalizar o estilo para seu caso de uso, pois este tutorial se concentra principalmente na lógica de criação de um aplicativo full-stack.
Fonte: https://blog.logrocket.com/build-full-stack-app-next-js-supabase/
1660849320
When it comes to building and choosing frameworks for your next full-stack application, combining Next.js with Supabase is one of the best options to work with in my opinion.
Supabase is an open source Firebase alternative with a lot of powerful tools, including seamless authentication. As a developer, this is key to building a successful full-stack application.
Alongside authentication, Supabase comes with other features, such as a Postgres database, real-time subscriptions, and object storage. I believe that Supabase is one of the easiest backend-as-a-services to get started or integrate with.
In this article, we will learn how to build a full-stack app using Next.js and Supabase. We’ll talk about how to set up a Supabase project, configure the UI, and implement authentication and functionalities.
The concept of this app is for users to track and create workout activities based on specified parameters, edit these activities if there are any mistakes or necessary changes, and delete them if needed. Let’s get started!
See more at: https://blog.logrocket.com/build-full-stack-app-next-js-supabase/
1660785685
This project is a Next.js starter with:
It is basically what is presented in the Supabase + Next.js quickstart, just with TypeScript, and some minimal UI with TailwindCSS.
Below, the original README generated by Next.js:
This is a Next.js project bootstrapped with create-next-app
.
First, run the development server:
npm run dev
# or
yarn dev
Open http://localhost:3000 with your browser to see the result.
You can start editing the page by modifying pages/index.js
. The page auto-updates as you edit the file.
API routes can be accessed on http://localhost:3000/api/hello. This endpoint can be edited in pages/api/hello.js
.
The pages/api
directory is mapped to /api/*
. Files in this directory are treated as API routes instead of React pages.
To learn more about Next.js, take a look at the following resources:
You can check out the Next.js GitHub repository - your feedback and contributions are welcome!
The easiest way to deploy your Next.js app is to use the Vercel Platform from the creators of Next.js.
Check out our Next.js deployment documentation for more details.
Author: scastiel
Source code: https://github.com/scastiel/nextjs-typescript-supabase-tailwind-starter?
#nextjs #react #reactjs #javascript #supabase #tailwindcss
1655712000
サーバーレスコンピューティングは、ソフトウェア開発の世界で人気のあるトピックであり、それには正当な理由があります。柔軟に拡張できるアプリケーションを構築および実行するための、より効率的で費用効果の高い方法を約束します。
Supabaseはサーバーレスクラウドプラットフォームであり、開発者はサーバーなしで洗練されたWebアプリやモバイルアプリを構築できます。 Supabaseは最近、サーバーレス機能をアプリケーションに追加する簡単な方法を探している人のためのオプションとしてEdgeFunctionsを導入しました。エッジ関数はTypeScriptで記述されており、Supabase CLIを使用して29の地理的地域に配布および展開し、世界中のユーザーにリーチできます。
執筆時点では、Edge FunctionsはSupabaseでまだ実験段階であり、重大な変更が行われる可能性があります。それでも、この機能は、大量のサーバーリソースなしでより強力な機能を構築する手段として、開発者の間で急速に普及しています。
しかし、エッジ関数はどのように機能しますか?そして、サーバーレスコードをどのように記述しますか?この記事では、そのすべてとそれ以上をカバーします!
先に進む前に、まずEdge Functionsが内部でどのように機能するか、およびSupabaseが関数コードを実行するプラットフォームをどのように管理するかを調べてみましょう。
Supabase Edge Functionsは、安全なDeno環境で実行されます。これらは、 Deno Deployがホストするサービスを使用して手動で関与する必要なしに、わずか数秒で世界中に展開されます。これはすべてSupabaseによって処理されるため、基盤となるテクノロジーを気にすることなく、アプリケーションのロジックに完全に集中できます。
Supabase Edge Functionが着信要求を受信すると、要求は最初に「リレー」に到着します。リレーはリクエストのAPIゲートウェイとして機能し、ヘッダーで渡されたJWTを認証します。また、ロギングやレート制限などの追加機能も提供します。
関数の要求を受信した後、Relayは、デプロイメントIDと呼ばれる一意の識別子とともに関数に関する情報を取得し、それをDenoDeployプラットフォームに渡します。このプラットフォームは、機能コードを安全に実行し、応答をリレーに返します。リレーは、エンドユーザーによって受信されます。
それでは、サンプルのエッジ関数を作成してデプロイしましょう。
Supabase Edge Functionsの使用を開始するには、最初にSupabase CLIをインストールし、プロジェクトをセットアップする必要があります。次の手順を実行します:
npm install -g supabase
supabase login
supabase init
supabase link
--project-ref <your-project-ref>
Supabaseを使用して新しいエッジ関数を作成するには、プロジェクト内で次のコマンドを実行します。
supabase functions new hello
ここでは、という関数を作成していますhello
。
これにより、Supabaseフォルダー内にボイラープレート関数コードが作成されます/functions/hello/index.ts
。
import { serve } from "https://deno.land/std@0.131.0/http/server.ts";
console.log("Hello from Functions!");
serve(async (req) => {
const { name } = await req.json();
const data = {
message: `Hello ${name}!`,
};
return new Response(JSON.stringify(data), {
headers: { "Content-Type": "application/json" },
});
});
上記のブロックでわかるように、デフォルトの関数コードは非常に単純で、すぐにデプロイできます。このserve
関数はHTTPサーバーを作成し、着信要求のリッスンを開始します。
Supabaseを使用してEdgeFunctionをデプロイするには、次のコマンドを実行します。
supabase functions deploy hello
このfunctions deploy
コマンドは、関数コードをパッケージ化し、リモートのSupabaseプロジェクトにデプロイします。Supabaseダッシュボードの[呼び出し]で、プロジェクト名のURL(以下を参照)をクリックして詳細を確認します。
リクエストをコピーしてcurl
、端末から機能をテストできます。
Edge Functionをローカルで開発して実行するには、Dockerを使用してローカルマシンにSupabaseをセットアップする必要があります。システムにSupabaseを設定する際のヘルプについては、このガイドを確認してください。
次のコマンドを実行して、Supabaseプロジェクトを開始します。
supabase start
次に、次のhello
ように関数を開始します。
supabase functions serve hello
このコマンドは、関数のローカルサーバーを起動し、ローカルホストポート54321でリッスンします。
関数を呼び出すにはcurl
、端末からリクエストを送信します。
curl --request POST 'http://localhost:54321/functions/v1/hello' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs' \
--header 'Content-Type: application/json' \
--data '{ "name":"Vijit" }'
Bearer
トークンは認証のヘッダーに必要です。プロジェクトのANON
キー、SERVICE_ROLE
キー、またはユーザーのJWTトークンにすることができます。
Supabase Edge Functionsは、単純なCRUDアプリを構築するだけではありません。また、任意のデータベースに接続し、データをリアルタイムで処理し、複雑なワークフローを構築することもできます。
実際のユースケースを見てみましょう。TwilioメッセージングAPIを使用してSMSを送信するsend-message
新しいエッジ関数を作成します。
関数を作成するsend-message
には、次のコマンドを実行します。
supabase functions new send-message
デフォルトの機能コードは次の場所にあります。/functions/send-message/index.ts
TwilioメッセージングAPIを使用するには、TwilioアカウントのSIDキー、認証トークン、およびSMSの送信に使用される仮想番号が必要です。
プロジェクト内にファイルを作成し、.env
ファイルに次の値を追加します。
// .env
TWILIO_ACCOUNT_SID=
TWILIO_AUTH_TOKEN=
TWILIO_PHONE_NUMBER=
注意:これらのクレデンシャルをコードで公開したり、.env
ファイルをGitHubの履歴に追加したりしないでください。
次に、SMSペイロードを表すインターフェイスを定義します。
// ./send-message/types/sms.interface.ts
export interface Sms {
[index: string]: string;
From: string;
To: string;
Body: string;
}
TwilioSms
次に、 TwilioメッセージングAPIを使用してSMSを送信するヘルパークラスを作成します。クラスコンストラクターは、アカウントSIDと認証トークンを受け入れます。
SIDと認証トークンは一緒にエンコードされ、APIリクエストで認証ヘッダーとして渡されます。
// ./send-message/helpers/twilio-sms.ts
import * as base64 from "https://denopkg.com/chiefbiiko/base64/mod.ts";
import { Sms } from "../types/sms.interface.ts";
export class TwilioSms {
private authorizationHeader: string;
constructor(private accountSID: string, authToken: string) {
this.authorizationHeader =
"Basic " +
base64.fromUint8Array(
new TextEncoder().encode(accountSID + ":" + authToken)
);
}
async sendSms(payload: Sms): Promise<any> {
const res = await fetch(
"https://api.twilio.com/2010-04-01/Accounts/" +
this.accountSID +
"/Messages.json",
{
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
Authorization: this.authorizationHeader,
},
body: new URLSearchParams(payload).toString(),
}
);
const data = await res.json();
return data;
}
}
メインの関数ハンドラーで、メソッドを使用して環境変数をロードし、ヘルパーからクラスDeno.env.get()
をインポートする必要があります。TwilioSms
次に、このsendSms()
メソッドを使用して、リクエスト本文で指定された特定の携帯電話番号にテキストメッセージを送信します。
// ./send-message/index.ts
import { serve } from "https://deno.land/std@0.131.0/http/server.ts";
import { TwilioSms } from "./helpers/twilio-sms.ts";
const accountSid = Deno.env.get("TWILIO_ACCOUNT_SID") || "";
const authToken = Deno.env.get("TWILIO_AUTH_TOKEN") || "";
const fromMobile = Deno.env.get("TWILIO_PHONE_NUMBER") || "";
serve(async (req) => {
const { textMessage, toMobile } = await req.json();
const twilioClient = new TwilioSms(accountSid, authToken);
const message = await twilioClient.sendSms({
Body: textMessage,
From: fromMobile,
To: toMobile,
});
console.log({ message });
const data = {
isSuccess: false,
};
if (message.status === "queued") {
data.isSuccess = true;
}
return new Response(JSON.stringify(data), {
headers: { "Content-Type": "application/json" },
});
});
関数をローカルでテストするには、supabase functions serve
コマンドを実行.env
し、パラメーターにファイルパスを渡して--env-file
、関数が環境変数にアクセスできるようにします。
supabase functions serve send-message --env-file ./supabase/.env
次に、コマンドを使用しcurl
て関数を呼び出します。
curl -i --location --request POST 'http://localhost:54321/functions/v1/' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs' \
--header 'Content-Type: application/json' \
--data '{ "textMessage":"Hello Developer!", "toMobile": "+91XXXXXXXXXX" }'
ローカル環境変数をリモートSupabaseプロジェクトにプッシュするには、次のsupabase secrets set
コマンドを実行します。
supabase secrets set --env-file ./supabase/.env
関数がローカルでテストされ、デプロイする準備ができたら、次のsupabase functions deploy
コマンドを実行します。
supabase functions deploy send-message
Supabase Edge Functionsには多くの利点がありますが、この新機能で何ができるかについてはまだいくつかの制限があります。データ集約的で実行に時間がかかる可能性のあるサービスの場合は、Supabaseのデータベース関数を使用することを選択する必要があります。
執筆時点では、Edge Functionsはまだ実験段階であり、将来のアップデートで重大な変更が行われる予定です。現在、Edge Functionsは、ポート25、465、または587へのアウトバウンド接続を確立できません。また、この機能はPOSTリクエストのみをサポートし、HTMLレスポンスはサポートしません。最後に、ローカル開発では一度に1つのエッジ関数のみを提供できます。
Supabase Edge Functionsは、アプリの機能を拡張するための優れたソリューションです。それらを使用することで、通常は別のサーバー側アプリケーションを必要とする機能をアプリに追加できます。
この記事では、Supabase Edge Functionsがどのように機能するかを調査し、Edge Functionを作成、デプロイ、および実行する方法について説明しました。また、Edge Functionsを使用してサーバーなしでSMSを送信する、実際のユースケースについても説明しました。
インフラストラクチャを自分で管理せずにアプリにカスタム機能を追加する方法を探している場合は、SupabaseEdgeFunctionsをチェックする価値があります。この記事が、SupabaseとEdgeFunctionsを使用して独自のアプリケーションを構築するのに役立つことを願っています。
このストーリーは、もともとhttps://blog.logrocket.com/using-edge-functions-supabase-complete-guide/で公開されました
1655712000
La computación sin servidor es un tema popular en el mundo del desarrollo de software, ¡y por una buena razón! Promete una forma más eficiente y rentable de crear y ejecutar aplicaciones que escalan de manera elástica.
Supabase es una plataforma en la nube sin servidor que permite a los desarrolladores crear aplicaciones web y móviles sofisticadas sin servidores. Supabase ha presentado recientemente Edge Functions como una opción para aquellos que buscan una manera fácil de agregar funciones sin servidor a sus aplicaciones. Las funciones Edge están escritas en TypeScript y se pueden distribuir e implementar con Supabase CLI en 29 regiones geográficas para llegar a usuarios de todo el mundo.
En el momento de redactar este informe, las funciones de borde aún son experimentales en Supabase y es probable que haya cambios importantes. Aún así, esta función se está volviendo popular rápidamente entre los desarrolladores como un medio para crear funciones más potentes sin recursos masivos del servidor.
Pero, ¿cómo funcionan las funciones Edge? Y, ¿cómo se escribe código sin servidor? ¡En este artículo, cubriremos todo eso y más!
Antes de continuar, primero exploremos cómo funcionan las funciones de Edge bajo el capó y cómo Supabase administra la plataforma que ejecuta el código de la función.
Las funciones de borde de Supabase se ejecutan en el entorno seguro de Deno . Se implementan en todo el mundo en cuestión de segundos sin necesidad de intervención manual mediante el servicio alojado Deno Deploy . Todo esto lo maneja Supabase, por lo que puede concentrarse completamente en la lógica de su aplicación sin preocuparse por las tecnologías subyacentes.
Cuando una función de borde de Supabase recibe una solicitud entrante, la solicitud llega primero a un "relé". El relé actúa como una puerta de enlace API para la solicitud y autentica el JWT pasado en los encabezados. También proporciona algunas funcionalidades adicionales como el registro y la limitación de velocidad.
Después de recibir la solicitud de una función, Relay recupera información sobre la función junto con un identificador único, llamado Id. de implementación, y lo pasa a la plataforma Deno Deploy. Esta plataforma ejecuta de forma segura el código de función y devuelve la respuesta al relé, que luego es recibida por el usuario final.
Ahora, creemos e implementemos una función Edge de muestra.
Para comenzar con Supabase Edge Functions, primero deberá instalar Supabase CLI y configurar un proyecto. Sigue estos pasos:
npm install -g supabase
supabase login
supabase init
supabase link
--project-ref <your-project-ref>
Para crear una nueva función Edge con Supabase, ejecute el siguiente comando dentro de su proyecto:
supabase functions new hello
Aquí, estamos creando una función llamada hello
.
Esto crea un código de función repetitivo dentro de su carpeta Supabase en: /functions/hello/index.ts
.
import { serve } from "https://deno.land/std@0.131.0/http/server.ts";
console.log("Hello from Functions!");
serve(async (req) => {
const { name } = await req.json();
const data = {
message: `Hello ${name}!`,
};
return new Response(JSON.stringify(data), {
headers: { "Content-Type": "application/json" },
});
});
Como puede ver en el bloque anterior, el código de función predeterminado es bastante simple y está listo para implementarse. La serve
función crea un servidor HTTP y comienza a escuchar las solicitudes entrantes.
Para implementar una función Edge con Supabase, ejecute el siguiente comando:
supabase functions deploy hello
El functions deploy
comando empaquetará su código de función y lo implementará en el proyecto Supabase remoto. En el panel de control de Supabase, en Invocar, haga clic en la URL con el nombre de su proyecto (ver más abajo) para encontrar más detalles.
Puede copiar la curl
solicitud para probar su función desde la terminal.
Para desarrollar y ejecutar Edge Function localmente, deberá usar Docker para configurar Supabase en su máquina local. Puede consultar esta guía para obtener ayuda para configurar Supabase en su sistema.
Inicie el proyecto Supabase ejecutando el siguiente comando:
supabase start
A continuación, inicie la hello
función, así:
supabase functions serve hello
Este comando iniciará un servidor local para la función y escuchará en el puerto localhost 54321.
Para invocar la función, haga una curl
solicitud desde su terminal:
curl --request POST 'http://localhost:54321/functions/v1/hello' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs' \
--header 'Content-Type: application/json' \
--data '{ "name":"Vijit" }'
El Bearer
token se requiere en el encabezado para la autenticación. Puede ser la clave de su proyecto ANON
, la SERVICE_ROLE
clave o el token JWT de un usuario.
Supabase Edge Functions puede ayudarlo a hacer más que simplemente crear una aplicación CRUD simple; también le permiten conectarse a cualquier base de datos, procesar datos en tiempo real e incluso crear flujos de trabajo complejos.
Echemos un vistazo a un caso de uso práctico. Crearemos una nueva función Edge send-message
, que enviará un SMS mediante la API de mensajería de Twilio .
Para crear la send-message
función, ejecute el siguiente comando:
supabase functions new send-message
Encontrará el código de función predeterminado en/functions/send-message/index.ts
Para utilizar la API de mensajería de Twilio, necesitará la clave SID de la cuenta de Twilio, un token de autenticación y un número virtual que se utilizará para enviar el SMS.
Cree un .env
archivo dentro del proyecto y agregue los siguientes valores al archivo:
// .env
TWILIO_ACCOUNT_SID=
TWILIO_AUTH_TOKEN=
TWILIO_PHONE_NUMBER=
NB, asegúrese de no exponer estas credenciales en su código o agregar el .env
archivo a su historial de GitHub.
A continuación, defina una interfaz para representar la carga útil de SMS:
// ./send-message/types/sms.interface.ts
export interface Sms {
[index: string]: string;
From: string;
To: string;
Body: string;
}
Ahora, cree una clase auxiliar TwilioSms
para enviar un SMS utilizando la API de mensajería de Twilio. El constructor de la clase aceptará el SID de la cuenta y el token de autenticación.
El SID y los tokens de autenticación se codifican juntos y se pasarán como un encabezado de autorización en la solicitud de la API.
// ./send-message/helpers/twilio-sms.ts
import * as base64 from "https://denopkg.com/chiefbiiko/base64/mod.ts";
import { Sms } from "../types/sms.interface.ts";
export class TwilioSms {
private authorizationHeader: string;
constructor(private accountSID: string, authToken: string) {
this.authorizationHeader =
"Basic " +
base64.fromUint8Array(
new TextEncoder().encode(accountSID + ":" + authToken)
);
}
async sendSms(payload: Sms): Promise<any> {
const res = await fetch(
"https://api.twilio.com/2010-04-01/Accounts/" +
this.accountSID +
"/Messages.json",
{
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
Authorization: this.authorizationHeader,
},
body: new URLSearchParams(payload).toString(),
}
);
const data = await res.json();
return data;
}
}
En el controlador de función principal, deberá cargar las variables de entorno utilizando el Deno.env.get()
método e importar la TwilioSms
clase de los ayudantes.
A continuación, utilice el sendSms()
método para enviar el mensaje de texto al número de móvil especificado en el cuerpo de la solicitud.
// ./send-message/index.ts
import { serve } from "https://deno.land/std@0.131.0/http/server.ts";
import { TwilioSms } from "./helpers/twilio-sms.ts";
const accountSid = Deno.env.get("TWILIO_ACCOUNT_SID") || "";
const authToken = Deno.env.get("TWILIO_AUTH_TOKEN") || "";
const fromMobile = Deno.env.get("TWILIO_PHONE_NUMBER") || "";
serve(async (req) => {
const { textMessage, toMobile } = await req.json();
const twilioClient = new TwilioSms(accountSid, authToken);
const message = await twilioClient.sendSms({
Body: textMessage,
From: fromMobile,
To: toMobile,
});
console.log({ message });
const data = {
isSuccess: false,
};
if (message.status === "queued") {
data.isSuccess = true;
}
return new Response(JSON.stringify(data), {
headers: { "Content-Type": "application/json" },
});
});
Para probar la función localmente, ejecute el supabase functions serve
comando y pase la .env
ruta del archivo en el --env-file
parámetro para que la función pueda acceder a las variables de entorno.
supabase functions serve send-message --env-file ./supabase/.env
Ahora, use el curl
comando para invocar la función.
curl -i --location --request POST 'http://localhost:54321/functions/v1/' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs' \
--header 'Content-Type: application/json' \
--data '{ "textMessage":"Hello Developer!", "toMobile": "+91XXXXXXXXXX" }'
Para enviar sus variables de entorno locales al proyecto Supabase remoto, ejecute el supabase secrets set
comando:
supabase secrets set --env-file ./supabase/.env
Una vez que su función se pruebe localmente y esté lista para implementarse, ejecute el supabase functions deploy
comando:
supabase functions deploy send-message
Las funciones de Supabase Edge brindan muchos beneficios, pero aún existen algunas limitaciones en cuanto a lo que puede hacer con esta nueva característica. Para los servicios que consumen muchos datos y su ejecución puede llevar mucho tiempo, debe optar por utilizar las funciones de base de datos de Supabase .
En el momento de escribir este artículo, las funciones de Edge aún son experimentales y habrá cambios importantes en futuras actualizaciones. Edge Functions no puede realizar conexiones salientes a los puertos 25, 465 o 587 en este momento. Además, esta función solo admite solicitudes POST y no respuestas HTML. Por último, solo se puede servir una función perimetral a la vez en el desarrollo local.
Supabase Edge Functions es una excelente solución para ampliar la funcionalidad de su aplicación. Al usarlos, puede agregar funciones a su aplicación que normalmente requerirían una aplicación del lado del servidor separada.
En este artículo, investigamos cómo funcionan las funciones Edge de Supabase y observamos cómo crear, implementar y ejecutar una función Edge. También analizamos un caso de uso del mundo real, utilizando Edge Functions para enviar un SMS sin un servidor.
Si está buscando una manera de agregar funciones personalizadas a su aplicación sin tener que administrar la infraestructura usted mismo, definitivamente vale la pena echarle un vistazo a Supabase Edge Functions. Espero que este artículo lo ayude a comenzar a usar Supabase y Edge Functions para crear sus propias aplicaciones.
Esta historia se publicó originalmente en https://blog.logrocket.com/using-edge-functions-supabase-complete-guide/
1653535747
Supabase is an open source Firebase alternative. We're building the features of Firebase using enterprise-grade open source tools.
For full documentation, visit supabase.com/docs
To see how to Contribute, visit Getting Started
We are currently in Public Beta. Watch "releases" of this repo to get notified of major updates.
Supabase is a combination of open source tools. We’re building the features of Firebase using enterprise-grade, open source products. If the tools and communities exist, with an MIT, Apache 2, or equivalent open license, we will use and support that tool. If the tool doesn't exist, we build and open source it ourselves. Supabase is not a 1-to-1 mapping of Firebase. Our aim is to give developers a Firebase-like developer experience using open source tools.
Architecture
Supabase is a hosted platform. You can sign up and start using Supabase without installing anything. You can also self-host and develop locally.
Our approach for client libraries is modular. Each sub-library is a standalone implementation for a single external system. This is one of the ways we support existing tools.
Language | Client | Feature-Clients (bundled in Supabase client) | |||
---|---|---|---|---|---|
Supabase | PostgREST | GoTrue | Realtime | Storage | |
⚡️ Official ⚡️ | |||||
JavaScript (TypeScript) | supabase-js | postgrest-js | gotrue-js | realtime-js | storage-js |
💚 Community 💚 | |||||
C# | supabase-csharp | postgrest-csharp | gotrue-csharp | realtime-csharp | storage-csharp |
Dart (Flutter) | supabase-dart | postgrest-dart | gotrue-dart | realtime-dart | storage-dart |
Go | - | postgrest-go | - | - | - |
Java | - | - | gotrue-java | - | - |
Kotlin | - | postgrest-kt | gotrue-kt | - | - |
Python | supabase-py | postgrest-py | gotrue-py | realtime-py | - |
Ruby | supabase-rb | postgrest-rb | - | - | - |
Rust | - | postgrest-rs | - | - | - |
Swift | supabase-swift | postgrest-swift | gotrue-swift | realtime-swift | storage-swift |
Download Details:
Author: supabase
Source Code: https://github.com/supabase/supabase
License: Apache-2.0 license
#supabase #firebase
1653535683
Supabase is an open source Firebase alternative. We're building the features of Firebase using enterprise-grade open source tools.
For full documentation, visit supabase.com/docs
To see how to Contribute, visit Getting Started
We are currently in Public Beta. Watch "releases" of this repo to get notified of major updates.
Supabase is a combination of open source tools. We’re building the features of Firebase using enterprise-grade, open source products. If the tools and communities exist, with an MIT, Apache 2, or equivalent open license, we will use and support that tool. If the tool doesn't exist, we build and open source it ourselves. Supabase is not a 1-to-1 mapping of Firebase. Our aim is to give developers a Firebase-like developer experience using open source tools.
Architecture
Supabase is a hosted platform. You can sign up and start using Supabase without installing anything. You can also self-host and develop locally.
Our approach for client libraries is modular. Each sub-library is a standalone implementation for a single external system. This is one of the ways we support existing tools.
Language | Client | Feature-Clients (bundled in Supabase client) | |||
---|---|---|---|---|---|
Supabase | PostgREST | GoTrue | Realtime | Storage | |
⚡️ Official ⚡️ | |||||
JavaScript (TypeScript) | supabase-js | postgrest-js | gotrue-js | realtime-js | storage-js |
💚 Community 💚 | |||||
C# | supabase-csharp | postgrest-csharp | gotrue-csharp | realtime-csharp | storage-csharp |
Dart (Flutter) | supabase-dart | postgrest-dart | gotrue-dart | realtime-dart | storage-dart |
Go | - | postgrest-go | - | - | - |
Java | - | - | gotrue-java | - | - |
Kotlin | - | postgrest-kt | gotrue-kt | - | - |
Python | supabase-py | postgrest-py | gotrue-py | realtime-py | - |
Ruby | supabase-rb | postgrest-rb | - | - | - |
Rust | - | postgrest-rs | - | - | - |
Swift | supabase-swift | postgrest-swift | gotrue-swift | realtime-swift | storage-swift |
Download Details:
Author: supabase
Source Code: https://github.com/supabase/supabase
License: Apache-2.0 license
#supabase #firebase
1652947771
The much anticipated build is FINALLY HERE!
Join me as I build the Reddit 2.0 CLONE with REACT & NEXT.js, you'll learn how to do the following in this build:
👉 You’ll learn how to create a GraphQL backend directly connected to your PostgreSQL Supabase database effortlessly with Stepzen!
👉 You’ll be able to build a Login authentication flow with the Reddit API using NextAuth
👉 You’ll learn how to implement your first SQL relational database (Including how to use Primary & Foreign Keys)!
👉 You’ll learn how to write robust code with Typescript!
👉 You’ll learn how to leverage Next.js Server Side Rendering to get a FASTER page load time!
👉 You’ll learn to create your own API backend endpoints to communicate safely with your frontend!
👉 You’ll learn about Tailwind CSS and how to build this awesome Responsive website!
👉 You’ll be able to Deploy to Vercel and have the site online by the end of this tutorial!
+ SO MUCH MORE!
🖥️ CODE
Get the code for my builds here: https://links.papareact.com/github
🕐 TIMESTAMPS:
[COMING SOON]
#reactjs #nextjs #typescript #tailwindcss #postgresql #sql #databases #stepzen #graphql #supabase