Node.js dla frontendu — pierwszy serwer w 30 minut

Praktyczny start z Node.js dla frontendowców. npm, package.json, Express, middleware i obsługa błędów bez uproszczeń, które później bolą w prawdziwym API.

Opublikowano

15 sierpnia 2025 10:10

Czytanie

5 min czytania

Aktualizacja

15 kwietnia 2026 11:52

Jako frontend developer prawdopodobnie używasz Node.js codziennie — npm, webpack, vite, wszystkie te narzędzia działają na Node. Ale czy wiesz, jak napisać własny serwer?

Znajomość podstaw Node.js to wymaganie na większości stanowisk frontend. Nie musisz być ekspertem od backendu, ale powinieneś rozumieć jak działa HTTP i potrafić postawić prosty API, czyli Application Programming Interface, definiuje sposób komunikacji między aplikacjami lub modułami. endpoint.

W tym artykule zbudujemy działający serwer Express od zera. Praktycznie, bez zbędnej teorii.

Krótka odpowiedź: Node.js pozwala uruchamiać JavaScript po stronie serwera, a Express.js to najpopularniejszy framework do budowania API. Wystarczy kilkanaście linijek kodu, żeby postawić działający serwer REST z obsługą routingu, middleware i błędów. To umiejętność wymagana na większości stanowisk frontend i dobry punkt wejścia w fullstack development.

Czym jest Node.js?

Node.js to środowisko uruchomieniowe JavaScript poza przeglądarką. Pozwala uruchamiać JS na serwerze, w terminalu, wszędzie.

Kluczowe cechy:

  • V8 Engine — ten sam silnik JS co w Chrome
  • Single-threaded, event-driven — jak pętla zdarzeń w JavaScript
  • Non-blocking I/O — świetny do operacji sieciowych, plików i baz danych
  • npm — największy ekosystem pakietów na świecie

To ważne rozróżnienie: Node błyszczy przy I/O, ale ciężkie obliczenia CPU-bound zwykle wynosi się do workerów, osobnych usług albo innego runtime'u.

Instalacja Node.js

Sprawdź czy masz Node.js

Code
node --version   # np. v20.11.0
npm --version    # np. 10.2.4

Instalacja

Opcja 1: Oficjalna strona Pobierz LTS z nodejs.org

Opcja 2: nvm (zalecane) Node Version Manager pozwala mieć wiele wersji:

Code
# Instalacja Node.js przez nvm po skonfigurowaniu narzędzia
nvm install --lts
nvm use --lts

Szczegóły instalacji samego nvm najlepiej brać z jego aktualnej dokumentacji, bo sposób bootstrapu bywa różny między systemami.

Pierwszy skrypt Node.js

Utwórz plik hello.js:

Code
console.log('Hello from Node.js!')
 
// Dostęp do informacji o środowisku
console.log('Node version:', process.version)
console.log('Platform:', process.platform)
console.log('Current directory:', process.cwd())

Uruchom:

Code
node hello.js

Proste! Możesz uruchamiać dowolny JavaScript.

W przykładach niżej używam składni CommonJS (require), bo jest krótka i nadal bardzo częsta w prostych projektach Node. W nowych aplikacjach równie dobrze możesz spotkać ESM (import).

npm i package.json

Inicjalizacja projektu

Code
mkdir my-server
cd my-server
npm init -y

Tworzony jest package.json:

Code
{
  "name": "my-server",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

Instalowanie pakietów

Code
# Dodaj zależność (dependencies)
npm install express
 
# Dodaj zależność developerską (devDependencies)
npm install --save-dev nodemon
 
# Skrócone wersje
npm i express
npm i -D nodemon

Po instalacji:

  • Pakiety w node_modules/
  • Lista w package.jsondependencies / devDependencies
  • Wersje zablokowane w package-lock.json

Ważne komendy npm

Code
npm install          # zainstaluj wszystkie zależności z package.json
npm install <pkg>    # dodaj pakiet
npm uninstall <pkg>  # usuń pakiet
npm update           # zaktualizuj pakiety
npm run <script>     # uruchom skrypt z package.json
npm start            # skrót dla npm run start

Prosty serwer HTTP (bez Express)

Node.js ma wbudowany moduł http:

Code
// server-basic.js
const http = require('http')
 
const server = http.createServer((req, res) => {
  console.log(`${req.method} ${req.url}`)
  
  res.writeHead(200, { 'Content-Type': 'text/plain' })
  res.end('Hello World!')
})
 
const PORT = 3000
server.listen(PORT, () => {
  console.log(`Server running at http://localhost:${PORT}`)
})
Code
node server-basic.js

Otwórz http://localhost:3000 w przeglądarce.

Działa, ale dla prawdziwych aplikacji używamy frameworków jak Express.

Express.js — standard dla Node.js

Express to minimalistyczny framework web dla Node.js. Upraszcza routing, middleware, obsługę błędów.

Instalacja

Code
npm install express

Podstawowy serwer Express

Code
// index.js
const express = require('express')
const app = express()
const PORT = 3000
 
// Middleware do parsowania JSON
app.use(express.json())
 
// Route — strona główna
app.get('/', (req, res) => {
  res.send('Hello from Express!')
})
 
// Route — JSON response
app.get('/api/status', (req, res) => {
  res.json({ status: 'ok', timestamp: new Date() })
})
 
// Start serwera
app.listen(PORT, () => {
  console.log(`Server running at http://localhost:${PORT}`)
})
Code
node index.js

Teraz masz:

  • http://localhost:3000/ → "Hello from Express!"
  • http://localhost:3000/api/status → JSON

Dodaj script do package.json

Code
{
  "scripts": {
    "start": "node index.js",
    "dev": "nodemon index.js"
  }
}
Code
npm run dev   # auto-restart przy zmianach

Routing w Express

Metody HTTP

Code
// GET — pobierz dane
app.get('/api/users', (req, res) => {
  res.json([{ id: 1, name: 'Jan' }, { id: 2, name: 'Anna' }])
})
 
// POST — utwórz
app.post('/api/users', (req, res) => {
  const newUser = req.body  // dane z body żądania
  console.log('Creating user:', newUser)
  res.status(201).json({ id: 3, ...newUser })
})
 
// PUT — aktualizuj
app.put('/api/users/:id', (req, res) => {
  const { id } = req.params
  const userData = req.body
  res.json({ id, ...userData })
})
 
// DELETE — usuń
app.delete('/api/users/:id', (req, res) => {
  const { id } = req.params
  res.status(204).send()  // No Content
})

Parametry URL

Code
// Path parameters — /api/users/123
app.get('/api/users/:id', (req, res) => {
  const userId = req.params.id  // "123"
  res.json({ id: userId })
})
 
// Query parameters — /api/search?q=javascript&limit=10
app.get('/api/search', (req, res) => {
  const { q, limit = 10 } = req.query
  res.json({ query: q, limit: Number(limit) })
})

Request body

Code
app.use(express.json())  // parser JSON — wymagane!
 
app.post('/api/users', (req, res) => {
  const { name, email } = req.body
  
  if (!name || !email) {
    return res.status(400).json({ error: 'Name and email required' })
  }
  
  // Tutaj zapisałbyś do bazy...
  res.status(201).json({ id: Date.now(), name, email })
})

Middleware

Middleware to funkcje, które mają dostęp do req, res i next. Wykonują się w kolejności definiowania.

Struktura middleware

Code
function myMiddleware(req, res, next) {
  // Zrób coś z req/res
  console.log(`${req.method} ${req.url}`)
  
  // Przekaż do następnego middleware
  next()
}
 
app.use(myMiddleware)

Praktyczne przykłady

Code
// Logger — loguj każde żądanie
app.use((req, res, next) => {
  console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`)
  next()
})
 
// Weryfikacja API key
function requireApiKey(req, res, next) {
  const apiKey = req.headers['x-api-key']
  
  if (apiKey !== process.env.API_KEY) {
    return res.status(401).json({ error: 'Invalid API key' })
  }
  
  next()
}
 
// Użyj dla konkretnych routes
app.get('/api/protected', requireApiKey, (req, res) => {
  res.json({ secret: 'data' })
})
 
// Lub dla wszystkich /api/*
app.use('/api', requireApiKey)

Wbudowane middleware

Code
// Parsowanie JSON body
app.use(express.json())
 
// Parsowanie URL-encoded body (formularze HTML)
app.use(express.urlencoded({ extended: true }))
 
// Serwowanie statycznych plików
app.use(express.static('public'))  // folder /public

Popularne zewnętrzne middleware

Code
const cors = require('cors')
const helmet = require('helmet')
const morgan = require('morgan')
 
// CORS — Cross-Origin Resource Sharing
app.use(cors())  // wygodne na start, ale w produkcji zwykle zawężasz originy
 
// Helmet — nagłówki bezpieczeństwa
app.use(helmet())
 
// Morgan — lepszy logger
app.use(morgan('dev'))

Obsługa błędów

Try-catch w async routes

Code
app.get('/api/users/:id', async (req, res, next) => {
  try {
    const user = await findUserById(req.params.id)
    
    if (!user) {
      return res.status(404).json({ error: 'User not found' })
    }
    
    res.json(user)
  } catch (error) {
    next(error)
  }
})

Globalny error handler

Code
// Middleware błędów — 4 argumenty!
app.use((err, req, res, next) => {
  console.error('Unhandled error:', err)
  
  res.status(err.status || 500).json({
    error: err.message || 'Internal server error'
  })
})

W Express 4 błędy z asynchronicznych handlerów trzeba zwykle przekazać do next(error) samemu. W nowszych setupach bywa to opakowane helperem albo obsługiwane przez warstwę frameworka.

404 handler

Code
// Na końcu, przed error handlerem
app.use((req, res) => {
  res.status(404).json({ error: 'Not found' })
})

Kompletny przykład — REST API

To tylko demonstracja. Po restarcie procesu dane znikną, więc w realnej aplikacji w tym miejscu wchodzi baza danych.

Code
// index.js
const express = require('express')
const cors = require('cors')
 
const app = express()
const PORT = process.env.PORT || 3000
 
// Middleware
app.use(cors())
app.use(express.json())
app.use((req, res, next) => {
  console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`)
  next()
})
 
// "Baza danych" w pamięci
let users = [
  { id: 1, name: 'Jan', email: 'jan@example.com' },
  { id: 2, name: 'Anna', email: 'anna@example.com' },
]
 
// Routes
app.get('/', (req, res) => {
  res.json({ message: 'API is running' })
})
 
// GET /api/users — lista wszystkich
app.get('/api/users', (req, res) => {
  res.json(users)
})
 
// GET /api/users/:id — jeden user
app.get('/api/users/:id', (req, res) => {
  const user = users.find(u => u.id === parseInt(req.params.id))
  
  if (!user) {
    return res.status(404).json({ error: 'User not found' })
  }
  
  res.json(user)
})
 
// POST /api/users — utwórz
app.post('/api/users', (req, res) => {
  const { name, email } = req.body
  
  if (!name || !email) {
    return res.status(400).json({ error: 'Name and email required' })
  }
  
  const nextId = Math.max(0, ...users.map(user => user.id)) + 1
  const newUser = {
    id: nextId,
    name,
    email
  }
  
  users.push(newUser)
  res.status(201).json(newUser)
})
 
// PUT /api/users/:id — aktualizuj
app.put('/api/users/:id', (req, res) => {
  const id = parseInt(req.params.id)
  const index = users.findIndex(u => u.id === id)
  
  if (index === -1) {
    return res.status(404).json({ error: 'User not found' })
  }
  
  const { name, email } = req.body
  users[index] = { id, name, email }
  res.json(users[index])
})
 
// DELETE /api/users/:id — usuń
app.delete('/api/users/:id', (req, res) => {
  const id = parseInt(req.params.id)
  const index = users.findIndex(u => u.id === id)
  
  if (index === -1) {
    return res.status(404).json({ error: 'User not found' })
  }
  
  users.splice(index, 1)
  res.status(204).send()
})
 
// 404 handler
app.use((req, res) => {
  res.status(404).json({ error: 'Not found' })
})
 
// Error handler
app.use((err, req, res, next) => {
  console.error(err)
  res.status(500).json({ error: 'Internal server error' })
})
 
// Start
app.listen(PORT, () => {
  console.log(`Server running at http://localhost:${PORT}`)
})

Testowanie API

Code
# GET all users
curl http://localhost:3000/api/users
 
# GET single user
curl http://localhost:3000/api/users/1
 
# POST create user
curl -X POST http://localhost:3000/api/users \
  -H "Content-Type: application/json" \
  -d '{"name":"Piotr","email":"piotr@example.com"}'
 
# PUT update user
curl -X PUT http://localhost:3000/api/users/1 \
  -H "Content-Type: application/json" \
  -d '{"name":"Jan Updated","email":"jan.new@example.com"}'
 
# DELETE user
curl -X DELETE http://localhost:3000/api/users/2

Zmienne środowiskowe

Nie hardcoduj wrażliwych danych!

Code
npm install dotenv
Code
// Na początku index.js
require('dotenv').config()
 
const PORT = process.env.PORT || 3000
const API_KEY = process.env.API_KEY
Code
# .env (dodaj do .gitignore!)
PORT=3000
API_KEY=super-secret-key
DATABASE_URL=mongodb://localhost/mydb

FAQ

Czym różni się Node.js od przeglądarki?

Node.js to środowisko uruchomieniowe JavaScript działające poza przeglądarką — na serwerze, w terminalu, w skryptach automatyzacji. Nie ma dostępu do DOM ani API przeglądarki (np. window, document), ale za to posiada własne moduły do obsługi plików, sieci i systemu operacyjnego.

Czy muszę znać Node.js jako frontend developer?

Tak, znajomość podstaw Node.js jest wymagana na większości stanowisk frontend. Wszystkie popularne narzędzia (Vite, webpack, npm, Next.js) działają na Node. Umiejętność postawienia prostego API i rozumienie, jak działa serwer HTTP, to kompetencje, które odróżniają seniora od juniora.

Co to jest Express.js i do czego służy?

Express.js to minimalistyczny framework webowy dla Node.js, który upraszcza tworzenie serwerów HTTP. Zapewnia routing (mapowanie URL na handlery), middleware (funkcje przetwarzające requesty) i wygodną obsługę błędów. Jest to de facto standard dla prostych API w ekosystemie Node.

Czym jest middleware w Express?

Middleware to funkcja przyjmująca req, res i next, która wykonuje się przed docelowym handlerem route'u. Middlewares używa się do logowania, autoryzacji, parsowania body, obsługi CORS i innych zadań przekrojowych. Są uruchamiane w kolejności, w jakiej zostały zarejestrowane.

Jak obsługiwać błędy w Express.js?

Błędy synchroniczne Express łapie automatycznie, ale dla asynchronicznych handlerów trzeba przekazać błąd do next(error). Na końcu aplikacji rejestruje się globalny error handler z czterema parametrami (err, req, res, next), który zwraca odpowiednią odpowiedź HTTP.

Czym jest package.json i jak zarządzać zależnościami?

package.json to plik konfiguracyjny projektu Node.js — przechowuje metadane, listę zależności (dependencies, devDependencies) i skrypty uruchomieniowe. Zależności instaluje się poleceniem npm install <nazwa>, a wersje są blokowane w package-lock.json dla reprodukowalności środowiska.

Jak przechowywać wrażliwe dane jak klucze API w Node.js?

Nigdy nie wpisuj wrażliwych danych na stałe w kodzie. Używaj zmiennych środowiskowych: utwórz plik .env (dodaj do .gitignore!), zainstaluj pakiet dotenv i wczytaj go na początku aplikacji przez require('dotenv').config(). Dostęp do wartości uzyskujesz przez process.env.NAZWA_ZMIENNEJ.

Podsumowanie

PojęcieOpis
Node.jsJS poza przeglądarką
npmMenedżer pakietów
package.jsonKonfiguracja projektu
ExpressFramework web dla Node.js
MiddlewareFunkcje przetwarzające req/res
RouterMapowanie URL → handler

Co dalej?

  • Podłącz prawdziwą bazę danych (MongoDB, PostgreSQL)
  • Dodaj autentykację (JWT, czyli JSON Web Token, to podpisany token używany często do autoryzacji i przekazywania tożsamości użytkownika., Passport.js)
  • Walidacja danych (Joi, Zod)
  • Testy (Jest, Supertest)

Masz teraz działające API REST w ~100 liniach kodu. To fundament, na którym budujesz większe aplikacje. Poznaj też zasady projektowania REST API, żeby Twoje endpointy były profesjonalne.

Źródła i dokumentacja


Następny krok? Zbuduj fullstack app z Prisma + Next.js lub poznaj backend dla frontendowca — kompletny przegląd.

Pracuję z tym zawodowo.

Jeśli chcesz przełożyć ten temat na lepszą architekturę frontendu, uporządkować React lub Next.js i podnieść jakość pracy zespołu, skontaktuj się ze mną. Pomagam zamieniać wiedzę z artykułów w praktyczne decyzje technologiczne.

O autorze

Maciej Sala

Maciej Sala — project manager i frontendowiec z doświadczeniem w marketingu internetowym. Na co dzień pracuję z Reactem, Next.js i TypeScriptem, łącząc perspektywę produktową z praktycznym podejściem do kodu. Przez kilka lat związany z branżą gier wideo jako project manager i game designer.

Absolwent historii na Uniwersytecie Jagiellońskim i studiów podyplomowych z marketingu internetowego na Akademii Górniczo-Hutniczej w Krakowie. Poza pracą trenuje na siłowni, maluje figurki i realizuje własne projekty.

Biblioteka wiedzy

Czytaj dalej

Zobacz więcej wpisów
Astro.js vs Next.js — które narzędzie wybrać w 2026 roku?

Astro.js vs Next.js — które narzędzie wybrać w 2026 roku?

Fachowe porównanie Astro.js i Next.js z perspektywy developera pracującego na co dzień w Next.js. Architektura, wydajność, SEO, DX, koszty i konkretne use case — z benchmarkami i przykładami kodu.

Maciej Sala

Maciej Sala

Founder Strivelab