Jak tuhle příručku používat
Je to „kapesní“ referenční manuál s copy‑ready příkazy a šablonami. Otevři levé menu, n.jsš, co hledáš (např. vite, jwt, docker), a skrolni na sekci. V kódech klikni na Kopírovat.
Pozn.: Některé způsoby můžou mít víc variant. U projektu React dnes často používáme
Vite (rychlejší dev server). create‑react‑app je pořád použitelné, ale považované
za „legacy“. Zahrnuji obě cesty.
0) Předpoklady a instalace
Java 17
- Ověř: java -version
- SDKMAN (macOS/Linux): curl -s "https://get.sdkman.io" | bash
- Instalace: sdk install java 17.0.10-tem
Node.js (LTS)
- Ověř: node -v, npm -v
- Volitelně nvm: nvm install --lts
Nástroje
- Git: git --version
- IDE: IntelliJ IDEA, VS Code
- HTTP klient: curl/Postman/Insomnia
1) React — založení projektu
Varianta A — Vite (doporučeno)
npm create vite@latest my-react-app -- --template react
cd my-react-app
npm install
npm run dev # spuštění vývoje
npm run build # produkční build
npm run preview # lokální náhled buildu
Alternativy: --template react-swc, TypeScript: --template react-ts.
Varianta B — create-react-app (legacy, ale funguje)
npx create-react-app my-react-app
cd my-react-app
npm start
npm run build
Základní komponenta
import { useEffect, useState } from "react";
export default function App() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
(async () => {
try {
setLoading(true);
const res = await fetch("/api/users");
if (!res.ok) throw new Error("HTTP " + res.status);
setUsers(await res.json());
} catch (e) { setError(e.message); }
finally { setLoading(false); }
})();
}, []);
if (loading) return Načítám…
;
if (error) return Chyba: {error}
;
return (
Uživatelé
{users.map(u => - {u.name} ({u.email})
)}
);
}
Router (react-router-dom)
npm install react-router-dom
// main.jsx
import { createRoot } from 'react-dom/client'
import { createBrowserRouter, RouterProvider } from 'react-router-dom'
import App from './App'
import UserDetail from './UserDetail'
const router = createBrowserRouter([
{ path: '/', element: },
{ path: '/users/:id', element: }
])
createRoot(document.getElementById('root')).render( )
Formulář + controlled inputs
function UserForm({ onSave, initial }) {
const [form, setForm] = useState(initial ?? { name: '', email: '' })
const submit = e => { e.preventDefault(); onSave(form) }
return (
<form onSubmit={submit}>
<input value={form.name} onChange={e => setForm({ ...form, name: e.target.value })} />
<input value={form.email} onChange={e => setForm({ ...form, email: e.target.value })} />
<button>Uložit</button>
</form>
)
}
Volání API — fetch & Axios
// fetch
await fetch('/api/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(user) })
// axios
import axios from 'axios'
const api = axios.create({ baseURL: '/api' })
const res = await api.get('/users')
Přidání Bootstrapu 5 do Reactu
npm install bootstrap
// main.jsx
import 'bootstrap/dist/css/bootstrap.min.css'
import 'bootstrap/dist/js/bootstrap.bundle.min.js'
1.1) React základní setup
Zobrazit data z API (fetch)
//api.js
export async function apiGet(endpoint) {
const url = `https://rickandmortyapi.com/api/${endpoint}`;
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Error fetching data:", error);
throw error; // Rethrow the error to be handled by the caller
}
}
1.2) Characters
Funkce pro postavy (načítaní listu)
//Characters.jsx
import {useEffect, useState} from "react";
import {apiGet} from "../utils/api.js";
import {Link} from "react-router-dom";
export default function Characters() {
const [characters, setCharacters] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchCharacter() {
try {
const data = await apiGet("character");
setCharacters(data.results || []); // ✅ pole
console.log(data);
} catch (e) {
console.error(e + "Error fetching characters");
setError(e.message + "Error fetching characters");
} finally {
setLoading(false);
}
}
fetchCharacter();
}, []);
if (loading) return <h2>Načítám...</h2>;
if (error) return <h2>Chyba: {error}</h2>;
return (
<div>
<h1>Postavy</h1>
{characters.map((character) => (
<div key={character.id}>
<h4>
<Link to={`/postavy/${character.id}`}>
{character.name}
</Link>
</h4>
<img
className="character"
src={character.image}
alt={character.name}
/>
</div>
))}
</div>
);
}
1.3) Epizody postavy
Funkce pro epizody postavy (načítaní jednoho)
//EpisodeDetail.jsx
import {useState, useEffect} from 'react';
import {Link, useParams} from "react-router-dom";
import {apiGet} from "../utils/api.js";
export default function EpisodeDetail() {
const {id} = useParams();
const [episode, setEpisode] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function load() {
try {
setLoading(true);
const data = await apiGet(`episode/${id}`);
setEpisode(data || []); // ✅ pole
console.log(data);
} catch (e) {
console.error(e + "Error fetching episode");
setError(e.message + "Error fetching episode");
} finally {
setLoading(false);
}
}
load();
}, [id]);
if (loading) return <h2>Načítám…</h2>;
if (error) return <h2>Chyba: {error}</h2>;
if (!episode) return <h2>Epizoda nenalezena</h2>;
return (
<div>
<h1>{episode.episode} — {episode.name}</h1>
<h3>Postavy v epizodě</h3>
<ul>
{episode.characters.slice(0, 20).map((url) => {
const characterId = url.split("/").pop();
return (
<li key={characterId}>
<img
src={`https://rickandmortyapi.com/api/character/avatar/${characterId}.jpeg`}
alt={`Postava ${characterId}`}
width={50}
height={50}
/>
<Link to={`/postavy/${characterId}`}>
Postava {characterId}
</Link>
</li>
);
})}
</ul>
</div>
);
}
1.4) Detail postavy
Funkce pro detailní informace postavy (načítaní jednoho plus načtení epizod)
//Detail.jsx
import { useEffect, useState } from "react";
import { useParams, Link } from "react-router-dom";
import { apiGet } from "../utils/api.js";
export default function Detail() {
const { id } = useParams();
const [character, setCharacter] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [episodes, setEpisodes] = useState([]);
useEffect(() => {
async function load() {
try {
setLoading(true);
setError(null);
const data = await apiGet(`character/${id}`); // 👈 použiješ id
setCharacter(data);
const episodeIds = data.episode.map((url) => url.split("/").pop());
if (episodeIds.length > 0) {
const epData = await apiGet(`episode/${episodeIds.join(",")}`);
setEpisodes(Array.isArray(epData) ? epData : [epData]);
}
} catch (e) {
setError(e.message);
} finally {
setLoading(false);
}
}
load();
}, [id]);
if (loading) return <h2>Načítám…</h2>;
if (error) return <h2>Chyba: {error}</h2>;
if (!character) return <h2>Postava nenalezena</h2>;
return (
<div className="detail">
<Link to="/postavy">⬅ Zpět</Link>
<h1>{character.name}</h1>
<img src={character.image} alt={character.name} />
<p>Status: {character.status}</p>
<p>Species: {character.species}</p>
<p>Gender: {character.gender}</p>
<p>Origin: {character.origin?.name}</p>
{episodes.length > 0 && (
<>
<h3>Epizody&
1.5) APP jsx pro Ricky and Morthy
Router dome
//App.jsx
import './App.css'
import {BrowserRouter, Link, Route, Routes} from "react-router-dom";
import Home from "./RickAndMorty/Home.jsx";
import Detail from "./RickAndMorty/Detail.jsx";
import Characters from "./RickAndMorty/Characters.jsx";
import EpisodeDetail from "./RickAndMorty/EpisodeDetail.jsx";
function App() {
return (
<>
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/postavy" element={<Characters />} />
<Route path="/postavy/:id" element={<Detail />} />
<Route path="/epizody/:id" element={<EpisodeDetail />} />
</Routes>
</BrowserRouter>
</>
)
}
export default App
2) Node.js / Express — rychlé API
Skeleton
npm init -y
npm install express cors dotenv zod
// index.js
const express = require('express')
const cors = require('cors')
const { z } = require('zod')
require('dotenv').config()
const app = express()
app.use(cors())
app.use(express.json())
let users = []
const User = z.object({ id: z.number().optional(), name: z.string().min(1), email: z.string().email() })
app.get('/api/users', (req, res) => res.json(users))
app.post('/api/users', (req, res) => {
const parsed = User.safeParse(req.body)
if (!parsed.success) return res.status(400).json(parsed.error)
const user = { id: Date.now(), ...parsed.data }
users.push(user)
res.status(201).json(user)
})
app.put('/api/users/:id', (req, res) => {
const id = Number(req.params.id)
const i = users.findIndex(u => u.id === id)
if (i < 0) return res.sendStatus(404)
users[i] = { ...users[i], ...req.body }
res.json(users[i])
})
app.delete('/api/users/:id', (req, res) => {
users = users.filter(u => u.id !== Number(req.params.id))
res.sendStatus(204)
})
const port = process.env.PORT || 3000
app.listen(port, () => console.log('API listening on', port))
Užitečné skripty
// package.json
{
"type": "module", // pro ES moduly (volitelné)
"scripts": {
"dev": "node index.js",
"start": "NODE_ENV=production node index.js"
}
}
Pozn.: Na Windows použij knihovnu cross-env pro
nastavování env proměnných.
3) Spring Boot — plný CRUD (Java 17)
Vytvoření projektu
- Přes Spring Initializr: závislosti Spring Web, Spring Data JPA,
Validation, Spring Security (volitelně), a driver DB (PostgreSQL/MySQL).
- Příkazem (Maven): mvn -v; build & run: mvn spring-boot:run
application.properties (PostgreSQL)
spring.datasource.url=jdbc:postgresql://localhost:5432/app
spring.datasource.username=app
spring.datasource.password=secret
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
# CORS (globální) — pro vývoj povolíme vše, v produkci upřesnit!
app.cors.allowed-origins=http://localhost:5173,http://localhost:3000
Konfigurace CORS (Java)
@Configuration
public class CorsConfig {
@Bean
public WebMvcConfigurer corsConfigurer(@Value("${app.cors.allowed-origins:*") String origins) {
return new WebMvcConfigurer() {
@Override public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedMethods("*").allowedOrigins(origins.split(","));
}
};
}
}
Entity + Validation
@Entity @Table(name = "users")
public class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank @Column(nullable = false)
private String name;
@Email @NotBlank @Column(nullable = false, unique = true)
private String email;
// get/set/ctor
}
Repository + Service
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
}
@Service
public class UserService {
private final UserRepository repo;
public UserService(UserRepository repo) { this.repo = repo; }
public Page<User> list(Pageable pageable) { return repo.findAll(pageable); }
public User create(@Valid User u) { return repo.save(u); }
public User update(Long id, User patch) {
User u = repo.findById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
if (patch.getName()!=null) u.setName(patch.getName());
if (patch.getEmail()!=null) u.setEmail(patch.getEmail());
return repo.save(u);
}
public void delete(Long id) { repo.deleteById(id); }
}
REST Controller (CRUD + stránkování)
@RestController @RequestMapping("/api/users")
public class UserController {
private final UserService svc;
public UserController(UserService svc) { this.svc = svc; }
@GetMapping
public Page<User> list(@PageableDefault(size=20, sort="id", direction = Sort.Direction.DESC) Pageable p) {
return svc.list(p);
}
@PostMapping @ResponseStatus(HttpStatus.CREATED)
public User create(@Valid @RequestBody User u) { return svc.create(u); }
@PutMapping("/{id}")
public User update(@PathVariable Long id, @RequestBody User patch) { return svc.update(id, patch); }
@DeleteMapping("/{id}") @ResponseStatus(HttpStatus.NO_CONTENT)
public void delete(@PathVariable Long id) { svc.delete(id); }
}
Global exception handler
@RestControllerAdvice
public class ApiErrors {
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
Map<String,Object> handleValidation(MethodArgumentNotValidException ex) {
Map<String,Object> body = new HashMap<>();
body.put("error", "validation_error");
body.put("fields", ex.getBindingResult().getFieldErrors()
.stream().collect(Collectors.toMap(FieldError::getField, DefaultMessageSourceResolvable::getDefaultMessage, (a,b) -> a)));
return body;
}
}
Spring Security — minimální JWT (schéma)
// build.gradle/maven: přidej jjwt-api a impl
// Konfigurace SecurityFilterChain – povol /auth/**, chraň /api/**, filtr pro ověření JWT v Authorization: Bearer <token>.
// V produkci používej rotaci klíčů a krátkou expiraci.
Záměrně zkráceno – JWT má mnoho detailů (klíče, expirace, refresh). V této příručce
držíme „minimum viable“ vzor.
4) Bootstrap 5 — layout, formuláře, komponenty
Rychlý start (CDN)
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
Grid a formulář
<div class="container">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Jméno</label>
<input class="form-control" />
</div>
<div class="col-md-6">
<label class="form-label">Email</label>
<input type="email" class="form-control" />
</div>
</div>
<button class="btn btn-primary mt-3">Uložit</button>
</div>
Tabulka
<table class="table table-striped table-hover">
<thead><tr><th>ID</th><th>Jméno</th><th>Email</th></tr></thead>
<tbody id="tbl"></tbody>
</table>
Modal
<button class="btn btn-secondary" data-bs-toggle="modal" data-bs-target="#m">Otevřít</button>
<div class="modal fade" id="m" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">...</div>
</div>
</div>
Toast
<div class="toast-container position-fixed bottom-0 end-0 p-3">
<div id="t" class="toast"><div class="toast-body">Uloženo!</div></div>
</div>
<script>new bootstrap.Toast(document.getElementById('t')).show()</script>
5) JavaScript — praktické vzory
Fetch helper
async function api(path, init) {
const res = await fetch('/api' + path, { headers: { 'Content-Type': 'application/json' }, ...init })
if (!res.ok) throw new Error('HTTP ' + res.status)
return res.headers.get('content-type')?.includes('json') ? res.json() : res.text()
}
// použití: await api('/users')
Debounce
const debounce = (fn, ms = 300) => { let t; return (...a) => { clearTimeout(t); t = setTimeout(() => fn(...a), ms) } }
FormData → JSON
const toJson = (form) => Object.fromEntries(new FormData(form).entries())
6) REST konvence, stránkování, filtrování
Adresy a metody
- GET /api/users — list (filter, page, size, sort)
- POST /api/users — create
- GET /api/users/{id} — detail
- PUT /api/users/{id} — update
- DELETE /api/users/{id} — delete
cURL příklady
curl -X POST http://localhost:8080/api/users -H 'Content-Type: application/json' \
-d '{"name":"Alice","email":"a@b.cz"}'
curl 'http://localhost:8080/api/users?page=0&size=20&sort=id,desc'
7) Databáze a migrace (PostgreSQL)
Rychlý start
# Docker
docker run --name pg -e POSTGRES_PASSWORD=secret -e POSTGRES_DB=app -p 5432:5432 -d postgres:16
Flyway migrace (Maven)
# src/main/resources/db/migration/V1__init.sql
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE
);
# pom.xml: <dependency> org.flywaydb:flyway-core </dependency>
# application.properties: spring.flyway.enabled=true
8) Testování
Spring — JUnit 5
@SpringBootTest
class UserControllerTest {
@Autowired MockMvc mvc;
@Test void createUser() throws Exception {
mvc.perform(post("/api/users").contentType(MediaType.APPLICATION_JSON)
.content("{\\"name\\":\\"A\\",\\"email\\":\\"a@b.cz\\"}"))
.andExpect(status().isCreated());
}
}
React — Jest + React Testing Library
import { render, screen } from '@testing-library/react'
import App from './App'
test('zobrazí nadpis', () => {
render(<App />)
expect(screen.getByText(/Uživatelé/i)).toBeInTheDocument()
})
9) Build & deploy (Docker)
Spring Boot Dockerfile
# Dockerfile (multi-stage)
FROM maven:3.9-eclipse-temurin-17 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn -q -e -DskipTests dependency:go-offline
COPY src ./src
RUN mvn -q -DskipTests package
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY --from=build /app/target/*jar app.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app/app.jar"]
React (Vite) — statický build + Nginx
# docker build -t react-app .
# docker run -p 8081:80 react-app
# Dockerfile
FROM node:20 AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:1.27-alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
docker-compose (API + DB + Frontend)
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
POSTGRES_DB: app
ports: ["5432:5432"]
api:
build: ./api
environment:
SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/app
SPRING_DATASOURCE_USERNAME: postgres
SPRING_DATASOURCE_PASSWORD: secret
ports: ["8080:8080"]
depends_on: [db]
web:
build: ./web
ports: ["5173:80"]
depends_on: [api]
10) Troubleshooting checklist
- 💥 CORS: povol správné Origin (viz CORS config výš).
- 🔑 JWT: chybí Authorization: Bearer <token>.
- 🗄️ DB: přístupová práva/port, správný driver a URL.
- 🧱 404 na SPA routách: použij SPA fallback v Nginxu.
- 🧪 Testy: nezapomeň na spring-boot-starter-test a @SpringBootTest.
11) Cheat‑sheet příkazy
# React (Vite)
npm create vite@latest myapp -- --template react
cd myapp && npm i && npm run dev
# React (CRA)
npx create-react-app myapp
cd myapp && npm start
# Spring Boot
mvn spring-boot:run
# nebo
./mvnw spring-boot:run
# Express skeleton
npm init -y
npm i express cors dotenv zod
node index.js
# Docker Postgres
docker run --name pg -e POSTGRES_PASSWORD=secret -e POSTGRES_DB=app -p 5432:5432 -d postgres:16
# cURL test
curl http://localhost:8080/api/users
12) Mini‑glosář
- CRUD — Create/Read/Update/Delete, základní operace nad daty.
- SPA — Single Page Application (React, Vue, …)
- CSR/SSR — Client/Server‑Side Rendering.
- DTO — objekt pro přenos dat mezi vrstvami.
- ORM — mapování objektů na tabulky (JPA/Hibernate).