Od pomysłu do pierwszej aplikacji – co naprawdę jest potrzebne na start
Aplikacja webowa vs zwykła strona – o co w ogóle chodzi
Zwykła strona internetowa to głównie treść: tekst, obrazki, proste menu, kilka podstron. Użytkownik wchodzi, czyta, przewija, czasem kliknie w link. Strona rzadko „zapamiętuje” użytkownika, niczego za niego nie liczy ani nie przetwarza.
Aplikacja webowa to coś więcej: użytkownik wchodzi, coś wprowadza, aplikacja to przetwarza i zwraca wynik. Dane się zmieniają, często są zapisywane, użytkownik może wrócić i zobaczyć własne informacje. Przykłady: lista zadań, prosty CRM, kalkulator rat, mały panel do notatek.
Dla początkującego najważniejsza różnica jest praktyczna: aplikacja webowa wymaga myślenia o logice (co się dzieje po kliknięciu) i o danych (co i gdzie zapisujesz). Nie potrzebujesz wodotrysków graficznych. Potrzebujesz prostego pomysłu, który da się „przeklikać” w głowie od początku do końca.
Dobry pierwszy problem do rozwiązania – mały, konkretny, mierzalny
Największy błąd początkujących: biorą się za „klona Facebooka” albo „platformę do wszystkiego”. Kończy się na trzech plikach z kodem i frustracji. Pierwsza aplikacja webowa powinna rozwiązywać banalny, ale konkretny problem, np.:
- lista zadań z możliwością dodawania i oznaczania wykonania,
- kalkulator rat kredytu / kosztów abonamentu,
- prosta lista zakupów współdzielona przez kilka osób (na początek nawet bez logowania),
- mini-apka do notatek z tytułem i opisem,
- licznik nawyków – ile razy dzisiaj coś zrobiłem.
Każdy z tych pomysłów ma bardzo wyraźny początek i koniec działania użytkownika: wchodzę, robię X, widzę wynik. To kluczowe, żebyś mógł krok po kroku przełożyć pomysł na kod, zamiast się gubić w milionie pobocznych funkcji.
Zasada „najmniejszego sensownego projektu”
Efektywna pierwsza aplikacja webowa to nie „małe imperium SaaS”, tylko coś, co realistycznie zrobisz w 1–2 tygodnie po pracy. Najlepiej przyjąć zasadę: co jestem w stanie wykonać w 10–15 blokach po 1–2 godziny. Jeśli pomysł tego nie mieści – trzeba go przyciąć.
Przykład złego pomysłu na start: „system zarządzania projektami z czatem, tablicą kanban, załącznikami plików i powiadomieniami e-mail”. Do tego potrzebujesz frontendu, back-endu, bazy danych, obsługi plików, systemu użytkowników, powiadomień… Na pierwszy raz to proszenie się o porażkę.
Przykład dobrego pomysłu: „lista zadań, która działa w przeglądarce, zapisuje zadania w przeglądarce użytkownika i pozwala dodawać oraz usuwać pozycje”. Jeśli zostanie czas – dorzucasz oznaczanie wykonania i filtrowanie. Najpierw trzon, później dodatki.
Przykład: lista zadań zamiast „klona Facebooka”
Załóżmy, że marzy Ci się coś społecznościowego. Zamiast rzucać się na feed, komentarze, lajki i zdjęcia, zacznij od „osobistego Facebooka” – listy własnych „postów”, czyli zadań lub notatek. Minimalna wersja:
- pole tekstowe „Treść zadania”,
- przycisk „Dodaj”,
- lista zadań pod spodem,
- przycisk „Usuń” przy każdym zadaniu.
Backend? Na pierwszy strzał – niekoniecznie. Dane możesz trzymać w pamięci lub w localStorage przeglądarki. Potem ten sam pomysł rozszerzysz o logowanie, bazę danych, współdzielenie zadań. Najpierw musisz jednak przećwiczyć pełny cykl: interfejs → logika → dane → aktualizacja interfejsu.
Jak zapisać pomysł w 5–7 punktach, które prowadzą do kodu
Dobry opis pomysłu nie ma być biznesplanem, tylko listą konkretnych zachowań aplikacji. Prosty szablon:
- Użytkownik wchodzi na stronę i widzi: [lista elementów, które mają być widoczne].
- Użytkownik może zrobić: [akcja 1, np. „dodać nowe zadanie”].
- Po wykonaniu akcji 1 dzieje się: [opis, co ma się stać na ekranie].
- Dane aplikacji przechowuję: [na start „w pamięci / localStorage / pliku JSON”].
- Minimalny „sukces” aplikacji to: [co użytkownik uzna za „działa”].
Dla listy zadań taki zapis może wyglądać tak:
- Po wejściu na stronę widzę pole tekstowe, przycisk „Dodaj” i pustą listę.
- Mogę wpisać treść zadania i kliknąć „Dodaj”.
- Po kliknięciu moje zadanie pojawia się na liście pod spodem.
- Obok zadania widzę przycisk „Usuń”, który po kliknięciu usuwa zadanie z listy.
- Lista ma się zapisywać w przeglądarce, żeby po odświeżeniu zadania nie znikały.
Taki opis od razu sugeruje, jakie elementy HTML, jakie funkcje JS i jaką strukturę danych będziesz potrzebować. To jest poziom planowania, który realnie pomaga – i nie zajmuje więcej niż kilkanaście minut.
Minimalny zestaw narzędzi – środowisko pracy za 0 zł lub prawie za 0 zł
Darmowy edytor kodu, który nie będzie przeszkadzał
Na start nie potrzebujesz płatnego IDE ani kombajnu za setki złotych. W pełni wystarczy darmowy, wygodny edytor. Najrozsądniejsze opcje:
- Visual Studio Code (VS Code) – najpopularniejszy wybór. Działa na Windows, macOS i Linux. Ma podświetlanie składni, podpowiedzi, integrację z Git, wtyczki do wszystkiego. Na początku wystarczy go zainstalować „gołego”, bez zalewania się wtyczkami.
- VSCodium – wersja VS Code bez komponentów Microsoftu, open source. Jeśli wolisz bardziej „czystą” licencję, to dobra alternatywa, funkcjonalnie bardzo podobna.
- Edytory w przeglądarce (np. GitHub Codespaces w darmowym zakresie, StackBlitz, CodeSandbox) – świetne, jeśli nie chcesz nic instalować na komputerze i masz stabilny internet. To też sposób na programowanie z taniego laptopa lub Chromebooka.
Dla początkującego liczy się niskie tarcie: otwierasz edytor, tworzysz plik index.html, zapisujesz i widzisz efekty w przeglądarce. Bez konfiguracji projektów, bez generatorów. Minimum kliknięć od pomysłu do pierwszej linijki kodu.
Przeglądarka jako główne narzędzie – DevTools i konsola
Każdy programista webowy spędza w przeglądarce mnóstwo czasu. Najlepiej zacząć od Chrome lub Firefoksa, bo mają rozbudowane DevTools (narzędzia deweloperskie). W nich znajdziesz m.in.:
- Inspektor – podgląd struktury HTML i CSS, możliwość podglądu i tymczasowej zmiany stylów.
- Konsolę – miejsce, gdzie możesz wpisywać komendy JS, sprawdzać logi (
console.log) i błędy. - Zakładkę Network – podgląd zapytań do backendu, gdy dojdziesz do etapu serwera.
Nawet pierwszą aplikację webową warto od razu „przyzwyczajać” do pracy z DevTools. Zamiast zgadywać, czemu coś nie działa, otwierasz konsolę i patrzysz, czy wyskoczył błąd. Taka nawykowa diagnostyka oszczędza godziny zgadywania.
Git i GitHub/GitLab – bezpieczny backup i historia zmian
Nawet solo opłaca się używać Gita od początku, bo:
- masz historię zmian – możesz wrócić do poprzedniej wersji, jeśli coś popsujesz,
- masz zdalny backup na GitHubie lub GitLabie – awaria dysku nie kasuje Twojej pracy,
- uczyć się workflow od razu „jak dorośli” – później nie będzie przesiadki.
Minimalny, tani (w zasadzie darmowy) workflow dla początkującego:
- Załóż konto na GitHubie (lub GitLabie).
- Zainstaluj Gita na komputerze.
- W folderze projektu wykonaj
git init, potem regularniegit add .igit commit -m "opis". - Ustaw repozytorium zdalne i wypychaj zmiany:
git push.
Na tym etapie nie trzeba znać gałęzi, merge’owania i zaawansowanych rzeczy. Wystarczy wiedzieć, jak zapisać punkt przywracania i jak wysłać projekt do chmury.
Lokalny serwer developerski – kiedy i jak go użyć
Prosty plik index.html da się otworzyć po prostu w przeglądarce (dwuklik w plik). Jednak przy pierwszej poważniejszej aplikacji webowej pojawią się kwestie, które wymagają prostego serwera HTTP, np.:
- zapytania
fetch()do plików, - podgląd relatywnych ścieżek jak na prawdziwym serwerze,
- później – komunikacja z backendem.
Najprostsze opcje, bez skomplikowanej konfiguracji:
- W VS Code wtyczka Live Server – jednym kliknięciem odpalasz serwer, który serwuje pliki z Twojego folderu.
- W Pythonie: w folderze projektu komenda
python -m http.server 8000i masz serwer podhttp://localhost:8000. - W Node.js: prosty pakiet
servelubhttp-server, też odpalany jedną komendą.
Na start spokojnie wystarczy wtyczka Live Server albo wbudowany serwer w narzędziach online (CodeSandbox, StackBlitz). Konfigurację Nginxa i Apache zostaw na później.
Porządek w folderach projektu od pierwszego dnia
Bałagan w plikach zabija produktywność szybciej, niż brak wiedzy. Prosty, praktyczny układ folderów na pierwszą aplikację webową:
/– główny folder projektuindex.html– główny plik HTML/css– stylestyle.css
/js– skryptyapp.js
/img– grafiki (jeśli potrzebne)/data– pliki z danymi (np. JSON), jeśli używasz
Taki schemat wystarczy na wiele małych projektów. Później można go rozbudować o folder /backend czy /src, ale nie ma sensu komplikować na siłę. Liczy się to, żebyś zawsze wiedział, gdzie jest HTML, gdzie CSS, gdzie JS.

Podstawy frontendu – HTML, CSS i JavaScript w praktycznym, okrojonym zestawie
Szkielet HTML – kilka znaczników, które wystarczą na początek
HTML to szkielet Twojej aplikacji webowej. Nie musisz znać setek tagów. Na pierwszą aplikację spokojnie wystarczy kilkanaście elementów, a w praktyce użyjesz kilku:
<html>,<head>,<body>– struktura dokumentu,<h1>–<h3>– nagłówki,<p>– akapity tekstu,<button>,<input>,<form>– interakcja (przyciski, formularze),<ul>,<li>– listy elementów,<div>,<span>– proste kontenery do grupowania,<script>– podpinanie JavaScriptu,<link>– podpinanie CSS.
Prosty szablon index.html pod pierwszą aplikację webową może wyglądać tak:
Minimalny szablon HTML dla prostej aplikacji
Poniższy szablon wystarczy, żeby postawić pierwszą realną aplikację w przeglądarce. Zawiera podpięty plik CSS i JS oraz prosty układ pod listę zadań:
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Moja pierwsza aplikacja</title>
<link rel="stylesheet" href="css/style.css" />
</head>
<body>
<main class="app">
<h1>Lista zadań</h1>
<form id="task-form">
<input
type="text"
id="task-input"
placeholder="Wpisz zadanie..."
autocomplete="off"
/>
<button type="submit">Dodaj</button>
</form>
<ul id="task-list"></ul>
</main>
<script src="js/app.js"></script>
</body>
</html>
Na razie nie ma tu żadnej „magii”: formularz do wpisywania zadań, lista i podpięty skrypt. Cała logika wyląduje w app.js.
Praktyczne minimum CSS – żeby aplikacja nie straszyła
Nie trzeba od razu znać flexboxa w 100% ani projektować jak senior frontend. Kilkanaście linijek CSS wystarczy, żeby aplikacja wyglądała schludnie i nie męczyła oczu. Przykład minimalnego pliku css/style.css:
/* Prosty reset, żeby przeglądarki zachowywały się podobnie */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: #f3f4f6;
color: #111827;
display: flex;
justify-content: center;
padding: 2rem;
}
.app {
background: #ffffff;
padding: 1.5rem;
border-radius: 0.75rem;
max-width: 480px;
width: 100%;
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.1);
}
h1 {
font-size: 1.5rem;
margin-bottom: 1rem;
}
#task-form {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
}
#task-input {
flex: 1;
padding: 0.5rem 0.75rem;
border-radius: 0.375rem;
border: 1px solid #d1d5db;
}
#task-input:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 1px #3b82f6;
}
button {
padding: 0.5rem 0.9rem;
border-radius: 0.375rem;
border: none;
background: #3b82f6;
color: #ffffff;
cursor: pointer;
}
button:hover {
background: #2563eb;
}
#task-list {
list-style: none;
display: flex;
flex-direction: column;
gap: 0.4rem;
}
.task-item {
display: flex;
justify-content: space-between;
align-items: center;
background: #f9fafb;
padding: 0.5rem 0.75rem;
border-radius: 0.375rem;
border: 1px solid #e5e7eb;
}
.task-text {
flex: 1;
margin-right: 0.5rem;
}
.task-remove {
background: #ef4444;
}
.task-remove:hover {
background: #dc2624;
}
.task-done .task-text {
text-decoration: line-through;
color: #6b7280;
}
Stylowanie można później rozwinąć, ale taki zestaw już sprawia, że efekt w przeglądarce wygląda „jak aplikacja”, a nie jak surowy prototyp. To dobry kompromis między czasem a estetyką.
JavaScript w wersji „tylko to, co potrzebne”
Na pierwszą aplikację przyda się garść powtarzalnych schematów JS, zamiast dziesiątek teorii:
- odczyt elementu z DOM (
document.querySelector), - podpinanie obsługi zdarzeń (
addEventListener), - tworzenie nowych elementów (
document.createElement), - prosta struktura danych w pamięci (np. tablica obiektów),
- zapisywanie danych w
localStorage.
Przykładowy minimalny plik js/app.js dla listy zadań:
const form = document.querySelector("#task-form");
const input = document.querySelector("#task-input");
const list = document.querySelector("#task-list");
let tasks = [];
// Wczytanie zadań z localStorage przy starcie
loadTasksFromStorage();
renderTasks();
form.addEventListener("submit", function (event) {
event.preventDefault();
const text = input.value.trim();
if (!text) return;
const newTask = {
id: Date.now(),
text,
done: false,
};
tasks.push(newTask);
saveTasksToStorage();
renderTasks();
input.value = "";
input.focus();
});
function renderTasks() {
list.innerHTML = "";
tasks.forEach(function (task) {
const li = document.createElement("li");
li.className = "task-item" + (task.done ? " task-done" : "");
li.dataset.id = task.id;
const span = document.createElement("span");
span.className = "task-text";
span.textContent = task.text;
const toggleBtn = document.createElement("button");
toggleBtn.type = "button";
toggleBtn.textContent = task.done ? "Cofnij" : "Zrobione";
const removeBtn = document.createElement("button");
removeBtn.type = "button";
removeBtn.textContent = "Usuń";
removeBtn.className = "task-remove";
toggleBtn.addEventListener("click", function () {
toggleTask(task.id);
});
removeBtn.addEventListener("click", function () {
removeTask(task.id);
});
li.appendChild(span);
li.appendChild(toggleBtn);
li.appendChild(removeBtn);
list.appendChild(li);
});
}
function toggleTask(id) {
tasks = tasks.map(function (task) {
if (task.id === id) {
return { ...task, done: !task.done };
}
return task;
});
saveTasksToStorage();
renderTasks();
}
function removeTask(id) {
tasks = tasks.filter(function (task) {
return task.id !== id;
});
saveTasksToStorage();
renderTasks();
}
function saveTasksToStorage() {
localStorage.setItem("tasks", JSON.stringify(tasks));
}
function loadTasksFromStorage() {
const raw = localStorage.getItem("tasks");
if (!raw) {
tasks = [];
return;
}
try {
tasks = JSON.parse(raw);
} catch (e) {
console.error("Błędny format danych w localStorage", e);
tasks = [];
}
}
To już jest kompletna, używalna aplikacja po stronie przeglądarki: dodaje, usuwa, oznacza zadania, zapisuje je między odświeżeniami. Bez backendu, bez frameworków. Na tym etapie lepiej nauczyć się dobrze tego prostego przepływu niż skakać od razu do Reacta.
Najczęstsze drobne błędy w pierwszym JS i jak je wychwycić
Przy takim kodzie zwykle pojawiają się powtarzalne potknięcia. Zamiast spędzać godziny na zgadywaniu, lepiej od razu zaglądać do konsoli w DevTools. Typowe problemy:
- Literówka w selektorze –
document.querySelector("#task-lsit")zamiast#task-list, efekt:nulli błąd przy.addEventListener. - Brak
event.preventDefault()w obsłudze formularza – strona się przeładowuje przy każdymsubmit, więc aplikacja „miga” i nic nie zostaje. - Nadpisanie tablicy
taskspojedynczym obiektem przez pomyłkowetasks = newTaskzamiasttasks.push(newTask).
Najtańszy czasowo nawyk: gdy coś nie działa, pierwsze kliknięcie idzie w F12 → Console, dopiero potem w Stack Overflow czy ChatGPT.
Backend bez bólu głowy – prosty serwer na start
Frontend „sam w sobie” a potrzeba backendu
Front w przeglądarce wystarczy na wiele prostych projektów solo albo do nauki. W pewnym momencie pojawia się jednak potrzeba serwera:
- kilku użytkowników ma współdzielić te same dane,
- dostęp z różnych urządzeń ma widzieć ten sam stan,
- trzeba ukryć klucze API albo logikę, której nie wolno ujawnić w przeglądarce.
Na tym etapie najbardziej opłaca się wybrać technologię, która:
- jest darmowa,
- ma dużo materiałów w sieci,
- jest prosta do odpalenia lokalnie.
Najbardziej „budżetowe” wybory: Node.js z Express lub Python z Flask. Obydwa odpalisz na tanim laptopie, bez drogich licencji, a hosting za grosze lub w darmowym planie też się znajdzie.
Minimalny backend w Node.js (Express) – od zera do działającego endpointu
Jeśli masz już zainstalowany Node.js, prosty backend zrobisz w kilku krokach:
- W folderze projektu utwórz podfolder
backend. - W terminalu w tym folderze odpal
npm init -y. - Zainstaluj Express:
npm install express. - Utwórz plik
server.jsz prostym serwerem.
Minimalna wersja server.js:
const express = require("express");
const cors = require("cors");
const app = express();
const PORT = 3000;
// Proste dane w pamięci (na start zamiast bazy)
let tasks = [];
// Middleware
app.use(cors());
app.use(express.json());
// Endpoint testowy
app.get("/", (req, res) => {
res.send("API działa");
});
// Pobierz wszystkie zadania
app.get("/tasks", (req, res) => {
res.json(tasks);
});
// Dodaj zadanie
app.post("/tasks", (req, res) => {
const text = (req.body.text || "").trim();
if (!text) {
return res.status(400).json({ error: "Brak treści zadania" });
}
const newTask = {
id: Date.now(),
text,
done: false,
};
tasks.push(newTask);
res.status(201).json(newTask);
});
// Zmień status zadania
app.patch("/tasks/:id/toggle", (req, res) => {
const id = Number(req.params.id);
const task = tasks.find((t) => t.id === id);
if (!task) {
return res.status(404).json({ error: "Nie znaleziono zadania" });
}
task.done = !task.done;
res.json(task);
});
// Usuń zadanie
app.delete("/tasks/:id", (req, res) => {
const id = Number(req.params.id);
const index = tasks.findIndex((t) => t.id === id);
if (index === -1) {
return res.status(404).json({ error: "Nie znaleziono zadania" });
}
const removed = tasks.splice(index, 1)[0];
res.json(removed);
});
app.listen(PORT, () => {
console.log(`Serwer działa na http://localhost:${PORT}`);
});
Serwer uruchamiasz komendą:
node server.jsTo już jest backend, który obsłuży CRUD na zadaniach. Dane są (na razie) w pamięci, więc po restarcie znikają – ale do nauki i pierwszego spięcia front–back to wystarczy.
Podłączenie frontendu do backendu – zamiana localStorage na API
Kolejny krok to zamiana zapisów do localStorage na zapytania HTTP. Zasada jest taka sama jak przy pracy na plikach, tylko zamiast localStorage.setItem pojawiają się wywołania fetch().
Przykładowa modyfikacja funkcji związanych z danymi w app.js (tym razem uproszczona wersja bez localStorage):
const API_URL = "http://localhost:3000";
async function loadTasksFromApi() {
const response = await fetch(API_URL + "/tasks");
if (!response.ok) {
throw new Error("Nie udało się pobrać zadań");
}
const data = await response.json();
tasks = data;
}
async function addTaskToApi(text) {
const response = await fetch(API_URL + "/tasks", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text }),
});
if (!response.ok) {
throw new Error("Nie udało się dodać zadania");
}
const created = await response.json();
tasks.push(created);
}
async function toggleTaskInApi(id) {
const response = await fetch(`${API_URL}/tasks/${id}/toggle`, {
method: "PATCH",
});
if (!response.ok) {
throw new Error("Nie udało się zmienić statusu zadania");
}
const updated = await response.json();
tasks = tasks.map((task) => (task.id === updated.id ? updated : task));
}
async function removeTaskFromApi(id) {
const response = await fetch(`${API_URL}/tasks/${id}`, {
method: "DELETE",
});
if (!response.ok) {
throw new Error("Nie udało się usunąć zadania");
}
await response.json();
tasks = tasks.filter((task) => task.id !== id);
}
Przy uruchamianiu aplikacji trzeba najpierw pobrać dane z API i dopiero potem je wyrenderować:
(async function init() {
try {
await loadTasksFromApi();
renderTasks();
} catch (e) {
console.error(e);
}
})();
Formularz obsługuje teraz funkcję asynchroniczną:
form.addEventListener("submit", async function (event) {
event.preventDefault();
const text = input.value.trim();
if (!text) return;
try {
await addTaskToApi(text);
renderTasks();
input.value = "";
input.focus();
} catch (e) {
console.error(e);
}
});
Takie przepisanie nie jest trudne, ale dobrze uczy myślenia w kategoriach „front wysyła, backend odpowiada”. To już bardzo przypomina to, co robi się w realnych projektach.
Alternatywa z Pythonem i Flaskiem – jeśli Node ci nie leży
Flask w praktyce – prosty serwer API w kilku plikach
Jeśli bliżej ci do Pythona (albo już go znasz z innych zastosowań), minimalny backend z Flaskiem wygląda podobnie do Expressa – inny jest tylko zapis składni.
- Zainstaluj Pythona 3 (jeśli jeszcze go nie masz).
- W folderze projektu utwórz katalog
backend-flask. - W tym katalogu stwórz i aktywuj wirtualne środowisko:
python -m venv venv # Windows: venvScriptsactivate # Linux / macOS: source venv/bin/activate - Zainstaluj Flask i flask-cors:
pip install Flask flask-cors - Utwórz plik
app.py:
from flask import Flask, request, jsonify
from flask_cors import CORS
app = Flask(__name__)
CORS(app)
# Dane w pamięci
tasks = []
@app.route("/")
def index():
return "API Flask działa"
@app.route("/tasks", methods=["GET"])
def get_tasks():
return jsonify(tasks)
@app.route("/tasks", methods=["POST"])
def add_task():
data = request.get_json(force=True) or {}
text = (data.get("text") or "").strip()
if not text:
return jsonify({"error": "Brak treści zadania"}), 400
new_task = {
"id": int(__import__("time").time() * 1000),
"text": text,
"done": False,
}
tasks.append(new_task)
return jsonify(new_task), 201
@app.route("/tasks/<int:task_id>/toggle", methods=["PATCH"])
def toggle_task(task_id):
for task in tasks:
if task["id"] == task_id:
task["done"] = not task["done"]
return jsonify(task)
return jsonify({"error": "Nie znaleziono zadania"}), 404
@app.route("/tasks/<int:task_id>", methods=["DELETE"])
def delete_task(task_id):
global tasks
before = len(tasks)
tasks = [t for t in tasks if t["id"] != task_id]
if len(tasks) == before:
return jsonify({"error": "Nie znaleziono zadania"}), 404
return jsonify({"success": True})
if __name__ == "__main__":
app.run(debug=True, port=5000)Serwer odpalasz z terminala:
python app.pyAdres API: http://localhost:5000. Po stronie frontendu zmienia się tylko API_URL, cała reszta logiki może zostać niemal identyczna.
Typowe pułapki przy pierwszym backendzie i jak z nimi wygrać
Początki z serwerem zwykle psują drobne detale, a nie „wielkie błędy architektury”. Kilka z nich pojawia się u prawie wszystkich:
- Porty – front na
http://localhost:5500(np. z Live Servera), backend na3000lub5000. Gdy wAPI_URLwpiszesz zły port, wszystko „niby działa”, alefetch()zwraca błędy. - Brak CORS – jeżeli nie dodasz
cors()/CORS(app), przeglądarka zablokuje zapytania na inny port. Gdy w konsoli pojawia się słowo „CORS”, trzeba dodać odpowiedni middleware. - JSON vs formularz – backend czeka na JSON, a
fetchwysyła dane w innym formacie lub bez nagłówkaContent-Type: application/json. Efekt: backend „dostaje pustkę”.
W razie kłopotu patrzysz na trzy rzeczy w tej kolejności: panel Network w DevTools, logi w terminalu backendu, dopiero potem eksperymenty w kodzie.

Dane w praktyce – przejście od pliku JSON do lekkiej bazy
Backend z danymi w pamięci jest jak notatka na kartce – przy restarcie wszystko znika. Kolejny minikrok to utrwalenie danych tak, żeby przeżywały restart serwera, ale bez wchodzenia w duże, drogie rozwiązania.
Najprostsze utrwalenie danych – zapis do jednego pliku JSON
Na start wystarczy plik na dysku. Jest tani (darmowy), łatwy do zrozumienia i bardzo podobny do localStorage, tylko zapis/odczyt robi backend.
Dla Node.js możesz zmodyfikować serwer tak, aby korzystał z pliku tasks.json:
const fs = require("fs");
const path = require("path");
const DATA_FILE = path.join(__dirname, "tasks.json");
function loadTasksFromFile() {
try {
const raw = fs.readFileSync(DATA_FILE, "utf-8");
return JSON.parse(raw);
} catch (e) {
console.log("Brak pliku lub błąd odczytu, startujemy z pustą listą");
return [];
}
}
function saveTasksToFile(tasks) {
fs.writeFileSync(DATA_FILE, JSON.stringify(tasks, null, 2), "utf-8");
}
let tasks = loadTasksFromFile();
// ...w środku endpointów po każdej zmianie:
tasks.push(newTask);
saveTasksToFile(tasks);Dla Flask wygląda to podobnie – używasz modułu json i funkcji, które czytają/zapisują plik przed/po modyfikacji listy.
Taki plikowy zapis wystarczy dla:
- prywatnych narzędzi – lista zadań, notatki, mały rejestr wydatków,
- prototypów – testujesz pomysł z dwoma-trzema osobami, bez ruchu z internetu.
Gdy liczba zapisów rośnie albo kilka procesów zaczyna jednocześnie pisać w ten sam plik, zaczynają się zgrzyty i wtedy pora na bazę danych.
SQLite – baza, która działa jak plik
SQLite to pragmatyczny krok: nadal jeden plik, ale z pełnoprawnymi tabelami i zapytaniami SQL. Nie trzeba osobnego serwera bazy, nie ma skomplikowanej konfiguracji, a do prostych aplikacji wystarcza na lata.
Wersja budżetowa dla Node.js: zainstaluj paczkę better-sqlite3:
npm install better-sqlite3Następnie uproszczony przykład integracji:
const Database = require("better-sqlite3");
const db = new Database("tasks.db");
// Jednorazowa inicjalizacja tabeli
db.exec(`
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
text TEXT NOT NULL,
done INTEGER NOT NULL DEFAULT 0
)
`);
function getAllTasks() {
return db.prepare("SELECT id, text, done FROM tasks").all()
.map((row) => ({
id: row.id,
text: row.text,
done: !!row.done,
}));
}
function createTask(text) {
const stmt = db.prepare("INSERT INTO tasks (text) VALUES (?)");
const info = stmt.run(text);
return { id: info.lastInsertRowid, text, done: false };
}
function toggleTask(id) {
const task = db.prepare("SELECT id, text, done FROM tasks WHERE id = ?").get(id);
if (!task) return null;
const newDone = task.done ? 0 : 1;
db.prepare("UPDATE tasks SET done = ? WHERE id = ?").run(newDone, id);
return { id: task.id, text: task.text, done: !!newDone };
}
function deleteTask(id) {
const info = db.prepare("DELETE FROM tasks WHERE id = ?").run(id);
return info.changes > 0;
}W endpointach zamiast operacji na tablicy wołasz teraz powyższe funkcje. Reszta frontu nie musi wiedzieć, czy pod spodem jest tablica, plik JSON czy tabela SQL – kontrakt API się nie zmienia.
Kiedy przestać kombinować z plikami i przejść na bazę
Przy kilku użytkownikach (nawet kilkunastu) i niewielkiej liczbie zapisów plik JSON przeżyje wszystko. Kłopoty pojawiają się, gdy:
- w tym samym momencie jest wiele zapisów – ryzyko utraty części danych przy kolizji,
- rekordów są tysiące, a ty chcesz filtrować, sortować, wyszukiwać,
- planowane jest wdrożenie na hostingu, który ma ograniczenia na zapisy plików.
Prosta zasada: jeśli już czujesz, że potrzebujesz wyszukiwarki i bardziej złożonych zapytań, zamiast wymyślać obejścia na plikach, lepiej od razu wskoczyć w SQLite lub inną lekką bazę.
Projekt funkcjonalności krok po kroku – bez wpadania w wir „wiecznego planowania”
Najdroższe są miesiące spędzone na dopieszczaniu planu, którego nigdy nie zrealizujesz. Przy pierwszej aplikacji najbardziej opłaca się rozcinać problem na mikro-kawałki, które domkniesz w 1–2 wieczory.
Od „wielkiego pomysłu” do listy minimalnych ekranów
Zamiast zaczynać od detali technicznych, rozpisz niewielką listę ekranów, które absolutnie muszą istnieć. Dla prostej aplikacji to zwykle:
- ekran główny (lista elementów, np. zadań, wpisów, produktów),
- formularz dodawania,
- ewentualnie ekran szczegółów jednego elementu (często można go pominąć).
Na kartce lub w pliku tekstowym spisz: co użytkownik może zrobić na każdym ekranie. Bez designu, bez kolorów, same czynności: „dodać zadanie”, „oznaczyć jako wykonane”, „usunąć”. Taka lista to twój mikro-roadmap – nic wyszukanego, ale wystarczająco konkretna, żeby wrócić po pracy i wiedzieć, co dziś robić.
Wycinanie „fajnych” funkcji – jak ciąć bez żalu
Największy wróg pierwszej aplikacji to „przy okazji dodam jeszcze…”. Szybko z prostego narzędzia robi się kombajn, którego nie kończysz nigdy. Dobrze działa taki filtr:
- czy funkcja jest potrzebna, żeby ktoś mógł korzystać z aplikacji w ogóle?
- czy można ją spokojnie dodać za tydzień bez przerabiania połowy kodu?
Jeżeli druga odpowiedź brzmi „tak”, ta funkcja ląduje w sekcji „później”. Przykład z listą zadań:
- priorytety, tagi, kolory – później,
- logowanie, profile – później,
- powiadomienia mailowe – zdecydowanie później.
Na start wystarczy: dodaj, pokaż, oznacz wykonane, usuń. Dopiero gdy ten rdzeń działa stabilnie, zerkasz na listę „później” i wybierasz jedną rzecz.
Minimalny backlog – prosty plik todo dla programisty
Zamiast pełnego systemu zarządzania projektami wystarczy zwykły plik TODO.md w repozytorium. W nim trzy krótkie listy:
## Do zrobienia
- Dodać walidację w formularzu (min. 3 znaki)
- Obsługa błędu przy braku połączenia z backendem
## W trakcie
- Refaktoryzacja renderTasks()
## Zrobione
- Proste API Express z CRUD dla tasks
- Podpięcie fetch() w fronciePrzy każdym krótkim „posiedzeniu” przekładasz tylko punkt między listami. Taki system ma zerowy koszt utrzymania, a pozwala widzieć postęp i nie trzymać wszystkiego w głowie.
Iteracyjne rozwijanie – małe cykle zamiast jednej ogromnej wersji
Najbardziej produktywny schemat pracy nad pierwszą aplikacją to krótkie cykle:
- Wybierz 1 funkcję z listy „Do zrobienia”.
- Zapytaj siebie: jaki jest jej najprostszy sensowny wariant?
- Zaimplementuj, przetestuj na sobie, odnotuj w „Zrobione”.
Przykład: „walidacja formularza”. Wersja maks: podpowiedzi inline, kolory, komunikaty dla różnych błędów. Wersja minimalna: blokada pustego tekstu i krótkie info nad formularzem, gdy coś jest nie tak. Na tym etapie wygrywa wariant minimalny – różnica w kodzie to godzina vs trzy wieczory.
Techniczne cięcie funkcjonalności – flagi i komentarze
Jeśli chcesz przetestować coś większego, ale boisz się rozwalić działającą wersję, z pomocą przychodzi prosta flaga. Przykład w JS dla dwóch trybów wyświetlania:
const EXPERIMENTAL_GRID_VIEW = false;
function renderTasks() {
if (EXPERIMENTAL_GRID_VIEW) {
renderTasksGrid();
} else {
renderTasksList();
}
}Wersja eksperymentalna może być brzydka, niedokończona, ale dzięki fladze nie psuje podstawowego trybu. Zero rozbudowanych feature flagów, tylko prosty if.
Kiedy powiedzieć „koniec wersji 1.0”
Pierwsza wersja „gotowa” nie znaczy „idealna”. Sensowna definicja ukończenia dla małej aplikacji:
- możesz przejść pełny przepływ: wejść na stronę, dodać element, zobaczyć go, zmienić, usunąć,
- aplikacja przeżywa odświeżenie strony (localStorage, plik, baza),
- brak błędów krytycznych w konsoli przy typowych akcjach użytkownika.
W tym momencie domykasz „wersję 1.0”. Nowe pomysły lądują jako „1.1”, „1.2” itd. Taki prosty rytm pomaga nie grzebać bez końca w tej samej funkcji i faktycznie przesuwać się do przodu.
Najczęściej zadawane pytania (FAQ)
Od czego zacząć tworzenie pierwszej aplikacji webowej?
Najprościej zacząć od bardzo małego, konkretnego pomysłu, który da się zrealizować w 1–2 tygodnie po pracy. Zamiast „platformy do wszystkiego” wybierz jedną funkcję, np. listę zadań, prosty kalkulator rat albo mini-notatnik.
Następnie zapisz w 5–7 punktach, co dokładnie ma się dziać: co użytkownik widzi po wejściu, jakie ma przyciski, co dzieje się po kliknięciu i gdzie trzymasz dane (np. w localStorage). Taki opis od razu przekłada się na konkretne elementy HTML i funkcje w JavaScript.
Jaka jest różnica między aplikacją webową a zwykłą stroną internetową?
Zwykła strona to głównie treść: tekst, obrazki, odnośniki. Użytkownik czyta i najwyżej klika w linki, ale strona rzadko coś za niego liczy czy zapamiętuje. Po odświeżeniu wszystko wygląda tak samo.
Aplikacja webowa reaguje na działania użytkownika, przetwarza dane i często je zapisuje. Przykład: lista zadań, która przechowuje Twoje pozycje i pokazuje je po powrocie. Musisz więc myśleć nie tylko o wyglądzie, ale też o logice (co się dzieje po kliknięciu) i danych (co, gdzie i jak zapisujesz).
Jaki pierwszy projekt aplikacji webowej jest najlepszy dla początkującego?
Najbezpieczniejsza opcja na start to projekt typu „lista zadań”, „lista zakupów” albo „prosty notatnik”. Dają szybki efekt, nie wymagają backendu i łatwo je stopniowo rozbudowywać, gdy poczujesz się pewniej.
Dobre przykłady na pierwszą apkę:
- lista zadań z dodawaniem i usuwaniem pozycji, zapisywana w localStorage,
- kalkulator rat kredytu lub abonamentu z prostym formularzem,
- mini-apka do notatek: tytuł + opis, możliwość skasowania notatki.
Każdy z tych projektów ma jasny początek i koniec: użytkownik wchodzi, robi jedną rzecz, widzi wynik. To pozwala przećwiczyć pełny cykl: interfejs → logika → zapis danych → aktualizacja interfejsu.
Jakie narzędzia są potrzebne do stworzenia pierwszej aplikacji webowej?
Na start wystarczy darmowy zestaw: przeglądarka (Chrome lub Firefox), prosty edytor kodu (np. Visual Studio Code lub VSCodium) i Git do wersjonowania kodu. Nie ma sensu inwestować w płatne IDE, dopóki nie wiesz, czy w ogóle spodoba Ci się tworzenie aplikacji.
Minimalny, „budżetowy” zestaw wygląda tak:
- edytor kodu: VS Code / VSCodium albo edytor w przeglądarce (StackBlitz, CodeSandbox),
- przeglądarka z DevTools do podglądu HTML/CSS/JS i błędów w konsoli,
- Git + konto na GitHubie lub GitLabie jako darmowy backup projektu.
Czy do pierwszej aplikacji webowej potrzebny jest backend i baza danych?
Nie, na sam początek backend jest zbędny i zwykle tylko komplikuje sprawę. Prosty projekt spokojnie zrobisz wyłącznie w HTML, CSS i JavaScript, trzymając dane w localStorage przeglądarki lub w pamięci (dopóki karta jest otwarta).
Dopiero gdy opanujesz podstawy logiki po stronie przeglądarki i przestaniesz się gubić przy prostych funkcjach, warto dorzucić backend i bazę danych. Ten sam projekt listy zadań możesz wtedy rozszerzyć o logowanie użytkownika i współdzielenie zadań.
Jak zaplanować pierwszą aplikację, żeby jej nie „przeinwestować”?
Przyjmij zasadę „najmniejszego sensownego projektu”: coś, co realnie zrobisz w 10–15 krótkich sesjach po 1–2 godziny. Jeśli pomysł się w tym nie mieści, obetnij funkcje do absolutnego minimum.
Dobre pytania kontrolne:
- Czy wersja „minimum” ma tylko jedną główną funkcję (np. dodanie/wyświetlenie zadań)?
- Czy umiesz ją opisać w 5–7 prostych punktach z perspektywy użytkownika?
- Czy da się ją sensownie pokazać komuś po tygodniu pracy?
Jeśli choć na jedno odpowiadasz „nie”, to najpierw przytnij zakres, zamiast dokładać kolejne opcje.
Czy muszę od razu uczyć się Gita i GitHuba przy pierwszym projekcie?
Nie musisz, ale bardzo się to opłaca nawet przy małej apce. Git daje Ci historię zmian i możliwość cofnięcia się do działającej wersji, gdy „przekombinujesz” kod. GitHub lub GitLab rozwiązuje temat backupu – nie stracisz wszystkiego przez awarię dysku.
Na początek wystarczy prosta rutyna: git init w folderze projektu, potem co jakiś czas git add . i git commit z krótkim opisem, a na końcu git push do zdalnego repozytorium. Bez gałęzi, merge’ów i innych zaawansowanych rzeczy – to możesz dodać później.






