Supabase + React Native
Өгөгдлийн сан, нэвтрэлт, файл хадгалалт — backend шаардагдах бүх зүйлийг Supabase үнэгүй хангадаг. React Native апп-д Supabase холбохоос эхлээд өгөгдөл унших, бичих хүртэл энэ хичээлд бүгдийг сурна. PostgreSQL мэддэг хэрэггүй, JavaScript мэдлэгтэй л хангалттай.
Суулгах ба тохиргоо
npx expo install @supabase/supabase-js @supabase/ssr
npx expo install expo-secure-store
expo-secure-store нь Supabase session-г аюулгүй хадгалахад хэрэгтэй — AsyncStorage-с найдвартай.
lib/supabase.ts файл үүсгэнэ:
import { createClient } from "@supabase/supabase-js";
import * as SecureStore from "expo-secure-store";
import { Platform } from "react-native";
// SecureStore нь вэб дээр ажиллахгүй тул platform шалгана
const ExpoSecureStoreAdapter = {
getItem: (key: string) => {
if (Platform.OS === "web") return localStorage.getItem(key);
return SecureStore.getItemAsync(key);
},
setItem: (key: string, value: string) => {
if (Platform.OS === "web") return localStorage.setItem(key, value);
return SecureStore.setItemAsync(key, value);
},
removeItem: (key: string) => {
if (Platform.OS === "web") return localStorage.removeItem(key);
return SecureStore.deleteItemAsync(key);
},
};
const SUPABASE_URL = process.env.EXPO_PUBLIC_SUPABASE_URL!;
const SUPABASE_ANON_KEY = process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY!;
export const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
auth: {
storage: ExpoSecureStoreAdapter,
autoRefreshToken: true,
persistSession: true,
detectSessionInUrl: false, // React Native-д false байх ёстой
},
});
.env файлд Supabase холболтын мэдээлэл нэмнэ:
EXPO_PUBLIC_SUPABASE_URL=https://xxxxxxxx.supabase.co
EXPO_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
EXPO_PUBLIC_ угтвартай орчны хувьсагч нь client талд харагдана. Нууц мэдээллийг хэзээ ч EXPO_PUBLIC_ угтвартайгаар нэрлэж болохгүй.
Өгөгдөл унших ба харуулах
Supabase холбоосон бол .from() → .select() дарааллаар өгөгдлийн сангаас мэдээлэл татна:
import { useState, useEffect } from "react";
import { supabase } from "../lib/supabase";
import {
View,
Text,
FlatList,
ActivityIndicator,
StyleSheet,
} from "react-native";
export default function PostsList() {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchPosts() {
const { data, error } = await supabase
.from("posts")
.select("id, title, body, created_at")
.order("created_at", { ascending: false })
.limit(20);
if (error) {
setError(error.message);
} else {
setPosts(data);
}
setLoading(false);
}
fetchPosts();
}, []);
if (loading) {
return (
<View style={styles.center}>
<ActivityIndicator size="large" color="#22d3ee" />
</View>
);
}
if (error) {
return (
<View style={styles.center}>
<Text style={styles.errorText}>⚠️ {error}</Text>
</View>
);
}
return (
<FlatList
data={posts}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => (
<View style={styles.card}>
<Text style={styles.title}>{item.title}</Text>
<Text style={styles.body} numberOfLines={3}>
{item.body}
</Text>
<Text style={styles.date}>
{new Date(item.created_at).toLocaleDateString("mn-MN")}
</Text>
</View>
)}
contentContainerStyle={styles.list}
/>
);
}
const styles = StyleSheet.create({
center: {
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "#0b1120",
},
list: {
padding: 16,
backgroundColor: "#0b1120",
},
card: {
backgroundColor: "#0f172a",
borderRadius: 12,
padding: 16,
marginBottom: 12,
borderWidth: 1,
borderColor: "#1e293b",
gap: 6,
},
title: {
color: "#f1f5f9",
fontSize: 16,
fontWeight: "600",
},
body: {
color: "#94a3b8",
fontSize: 13,
lineHeight: 18,
},
date: {
color: "#475569",
fontSize: 11,
marginTop: 4,
},
errorText: {
color: "#fb7185",
fontSize: 15,
textAlign: "center",
padding: 24,
},
});
Өгөгдөл бичих ба устгах
insert, update, delete — CRUD үйлдлүүд адилхан хялбар:
import { useState } from "react";
import { supabase } from "../lib/supabase";
import {
View,
Text,
TextInput,
TouchableOpacity,
Alert,
StyleSheet,
} from "react-native";
export default function NewPost() {
const [title, setTitle] = useState("");
const [body, setBody] = useState("");
const [saving, setSaving] = useState(false);
const createPost = async () => {
if (!title.trim() || !body.trim()) {
Alert.alert("Анхааруулга", "Гарчиг болон агуулгыг оруулна уу");
return;
}
setSaving(true);
const { error } = await supabase
.from("posts")
.insert({ title: title.trim(), body: body.trim() });
if (error) {
Alert.alert("Алдаа", error.message);
} else {
Alert.alert("Амжилттай", "Нийтлэл хадгалагдлаа");
setTitle("");
setBody("");
}
setSaving(false);
};
const deletePost = async (id) => {
const { error } = await supabase.from("posts").delete().eq("id", id); // WHERE id = ?
if (error) {
Alert.alert("Алдаа", error.message);
}
};
const updatePost = async (id, newTitle) => {
const { error } = await supabase
.from("posts")
.update({ title: newTitle })
.eq("id", id);
if (error) {
Alert.alert("Алдаа", error.message);
}
};
return (
<View style={styles.container}>
<Text style={styles.heading}>Шинэ нийтлэл</Text>
<TextInput
style={styles.input}
value={title}
onChangeText={setTitle}
placeholder="Гарчиг"
placeholderTextColor="#475569"
/>
<TextInput
style={[styles.input, styles.textarea]}
value={body}
onChangeText={setBody}
placeholder="Агуулга..."
placeholderTextColor="#475569"
multiline
numberOfLines={5}
textAlignVertical="top"
/>
<TouchableOpacity
style={[styles.btn, saving && styles.btnDisabled]}
onPress={createPost}
disabled={saving}
>
<Text style={styles.btnText}>
{saving ? "Хадгалж байна..." : "Нийтлэх"}
</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 24,
backgroundColor: "#0b1120",
},
heading: {
fontSize: 22,
fontWeight: "bold",
color: "#f1f5f9",
marginBottom: 20,
},
input: {
backgroundColor: "#0f172a",
borderWidth: 1,
borderColor: "#1e293b",
borderRadius: 10,
padding: 14,
color: "#f1f5f9",
fontSize: 15,
marginBottom: 12,
},
textarea: {
height: 120,
},
btn: {
backgroundColor: "#22d3ee",
borderRadius: 10,
padding: 16,
alignItems: "center",
marginTop: 4,
},
btnDisabled: { opacity: 0.5 },
btnText: {
color: "#0b1120",
fontWeight: "700",
fontSize: 16,
},
});
Supabase-г React Native-тай холбосноор дутуу байсан backend бүхэлдээ бэлэн болно. Мэдээллийн сан, файл хадгалалт, нэвтрэлт — бүгдийг нэг package-р, үнэгүй, JavaScript-аар л удирдана. Дараагийн хичээлд authentication нэмэх замаар апп-г бүрэн болгоно.
Дараагийн хичээлд:
Supabase Auth React Native-д — бүртгэл, нэвтрэлт, session удирдалт, хамгаалагдсан дэлгэц хэрхэн хийх талаар сурна.