В этой лекции мы рассмотрим две критически важные части разработки веб приложений: систему контроля версий Git и облачную базу данных Supabase. Эти инструменты позволяют работать в команде, отслеживать изменения кода и хранить данные приложения в масштабируемой системе.
Git — это распределённая система контроля версий (VCS), которая позволяет отслеживать все изменения в коде. Каждый разработчик имеет полную копию истории проекта, что обеспечивает безопасность и гибкость.
Основные преимущества Git:
Гайд по установке Git: https://selectel.ru/blog/tutorials/how-to-install-git-to-windows/
# Проверьте, всё ли корректно установилось
git --version
# Укажите ваше имя и почту (из аккаунта GitHub)
git config --global user.name "Ваше Имя"
git config --global user.email "you@example.com"
# 1. Инициализировать репозиторий в текущей папке
git init
# 2. Посмотреть состояние (какие файлы изменены)
git status
# 3. Добавить файлы в индекс (staging area)
git add . # Добавить все файлы
git add index.tsx # Добавить конкретный файл
git add *.js # Добавить все JS файлы
# 4. Создать коммит (сохранение версии)
git commit -m "Описание изменений"
# 1. Подключить удалённый репозиторий
git remote add origin https://github.com/user/project.git
# 2. Проверить, какие удалённые репозитории подключены
git remote -v
# 3. Отправить изменения на GitHub (первый раз)
git push -u origin main
# 4. Отправить изменения (последующие разы)
git push
# 5. Получить изменения с GitHub
git pull
Этот файл указывает, какие файлы Git должен игнорировать (не отслеживать):
# Зависимости Node.js
node_modules/
# Переменные окружения
.env
.env.local
.env.*.local
# Логи
logs/
*.log
# IDE
.vscode/
.idea/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Build
dist/
build/
.next/
Supabase — это open-source платформа на основе PostgreSQL, которая предоставляет готовое решение для работы с базой данных, аутентификацией и хранилищем файлов.
Основные возможности:
npm install @supabase/supabase-js
В корне проекта создаётся файл .env:
NEXT_PUBLIC_SUPABASE_URL=https://your-project-id.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
Создаём файл, например lib/supabaseClient.ts:
// lib/supabaseClient.ts
import { createClient } from '@supabase/supabase-js';
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
export const supabase = createClient (supabaseUrl, supabaseAnonKey);
Supabase предоставляет интуитивный API для работы с базой данных. Все операции используют метод-построитель цепочки (builder pattern) и возвращают объект с полями data и error.
Метод .select() извлекает строки из таблицы. Можно выбрать все колонки или конкретные:
// Получить все данные
const { data, error } = await supabase
.from('tasks')
.select('*');
// Получить конкретные колонки
const { data, error } = await supabase
.from('tasks')
.select('id, title, completed');
Метод .insert() добавляет новые строки в таблицу. По умолчанию не возвращает данные, но можно добавить .select():
// Вставить одну строку
const { error } = await supabase
.from('tasks')
.insert({ title: 'Новая задача', completed: false });
// Вставить и получить возвращаемые данные
const { data, error } = await supabase
.from('tasks')
.insert({ title: 'Новая задача', completed: false })
.select();
// Вставить несколько строк сразу
const { data, error } = await supabase
.from('tasks')
.insert([
{ title: 'Задача 1', completed: false },
{ title: 'Задача 2', completed: true },
{ title: 'Задача 3', completed: false }
])
.select();
Метод .update() изменяет существующие строки. Обязательно нужно использовать фильтр (например .eq()), иначе обновятся все строки:
// Обновить конкретную задачу
const { error } = await supabase
.from('tasks')
.update({ completed: true })
.eq('id', 1);
// Обновить и получить результат
const { data, error } = await supabase
.from('tasks')
.update({ title: 'Обновленное название' })
.eq('id', 1)
.select();
// Обновить несколько строк по условию
const { error } = await supabase
.from('tasks')
.update({ completed: true })
.gt('priority', 5)
.select();
Метод .delete() удаляет строки. Как и в UPDATE, нужен фильтр:
// Удалить конкретную задачу
const { error } = await supabase
.from('tasks')
.delete()
.eq('id', 1);
// Удалить и получить удаленные строки
const { data, error } = await supabase
.from('tasks')
.delete()
.eq('id', 1)
.select();
// Удалить все завершенные задачи
const { error } = await supabase
.from('tasks')
.delete()
.eq('completed', true);
Фильтры ограничивают результаты запроса по условиям. Все методы фильтра цепляются в цепочку:
| Метод | Значение | Пример |
|---|---|---|
.eq(column, value) |
равно | .eq('status', 'active') |
.neq(column, value) |
не равно | .neq('id', 5) |
.gt(column, value) |
больше | .gt('age', 18) |
.gte(column, value) |
больше или равно | .gte('score', 100) |
.lt(column, value) |
меньше | .lt('price', 50) |
.lte(column, value) |
меньше или равно | .lte('age', 65) |
.like(column, pattern) |
совпадение (учитывает регистр) | .like('name', '%John%') |
.ilike(column, pattern) |
совпадение (игнорирует регистр) | .ilike('email', '%gmail%') |
.in(column, array) |
значение в списке | .in('status', ['active', 'pending']) |
.is(column, value) |
проверка null/true/false | .is('deleted_at', null) |
.contains(column, array) |
массив содержит значения | .contains('tags', ['react']) |
Примеры использования:
// Получить активные задачи
const { data } = await supabase
.from('tasks')
.select('*')
.eq('completed', false);
// Поиск по тексту (без учёта регистра)
const { data } = await supabase
.from('tasks')
.select('*')
.ilike('title', '%купить%');
// Задачи с приоритетом выше 5
const { data } = await supabase
.from('tasks')
.select('*')
.gte('priority', 5);
// Задачи, созданные в диапазоне дат
const { data } = await supabase
.from('tasks')
.select('*')
.gte('created_at', '2024-01-01')
.lte('created_at', '2024-12-31');
// Нескольких условий (логическое И)
const { data } = await supabase
.from('tasks')
.select('*')
.eq('user_id', userId)
.eq('completed', false)
.gte('priority', 7);
// Статус в списке значений
const { data } = await supabase
.from('tasks')
.select('*')
.in('status', ['todo', 'in_progress']);
Модификаторы изменяют формат и порядок результатов:
.order() — сортировка// Сортировка по возрастанию (по умолчанию)
const { data } = await supabase
.from('tasks')
.select('*')
.order('created_at', { ascending: true });
// Сортировка по убыванию
const { data } = await supabase
.from('tasks')
.select('*')
.order('priority', { ascending: false });
// Несколько полей сортировки
const { data } = await supabase
.from('tasks')
.select('*')
.order('priority', { ascending: false })
.order('created_at', { ascending: true });
Fetch API — это встроенный в браузер (и Node.js) механизм для отправки HTTP запросов. Fetch возвращает Promise, что делает его удобным для использования с async/await.
Основные преимущества:
Самый простой запрос:
// Базовый GET запрос
const response = await fetch('https://api.example.com/tasks');
const data = await response.json();
console.log(data);
С проверкой ошибок:
try {
const response = await fetch('https://api.example.com/tasks');
// Проверить статус ответа
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Ошибка при загрузке:', error);
}
const newTask = {
title: 'Новая задача',
completed: false,
priority: 5
};
const response = await fetch('https://api.example.com/tasks', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(newTask)
});
const data = await response.json();
console.log('Созданная задача:', data);
const taskId = 1;
const updatedTask = {
title: 'Обновленная задача',
completed: true,
priority: 3
};
const response = await fetch(`https://api.example.com/tasks/${taskId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(updatedTask)
});
const data = await response.json();
console.log('Обновленная задача:', data);
const taskId = 1;
const response = await fetch(`https://api.example.com/tasks/${taskId}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
}
});
if (response.ok) {
console.log('Задача удалена');
} else {
console.error('Ошибка удаления');
}
const response = await fetch(url);
const data = await response.json();
const response = await fetch(url);
const text = await response.text();
const response = await fetch(url);
const blob = await response.blob();
// Например, сохранить изображение
const img = document.createElement('img');
img.src = URL.createObjectURL(blob);
document.body.appendChild(img);
Важно помнить: fetch не выбрасывает ошибку на HTTP ошибках (404, 500), нужно проверять response.ok или response.status:
try {
const response = await fetch('https://api.example.com/tasks');
// Проверить статус
if (!response.ok) {
// Это может быть 404, 500, 401 и т.д.
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
// Сюда попадают:
// - Ошибки сети (нет интернета)
// - Ошибки парсинга JSON
// - Выброшенные ошибки (throw)
console.error('Ошибка:', error.message);
}