Jak Przygotować się do Rozmowy z Node.js - Kompletny Przewodnik
"Wyjaśnij dokładnie, jak działa event loop i dlaczego setTimeout z czasem 0 nie wykonuje się natychmiast." To pytanie potrafi zaskoczyć nawet programistów z wieloletnim doświadczeniem w Node.js. Większość developerów świetnie pisze kod i buduje działające API, ale nie potrafi wyjaśnić mechanizmów działających pod spodem.
Na rozmowach rekrutacyjnych z Node.js pojawia się powtarzający się wzorzec: kandydaci znają Express, potrafią zbudować REST API, ale nie wyjaśnią, dlaczego Node.js radzi sobie z tysiącami połączeń na jednym wątku. Nie wiedzą, jak naprawdę działa middleware ani czym różni się microtask od macrotask.
W tym artykule znajdziesz najważniejsze tematy pojawiające się na rozmowach rekrutacyjnych z Node.js. Bez egzotycznych przypadków - skupimy się na pytaniach, które naprawdę padają i które odróżniają kandydatów rozumiejących Node.js od tych, którzy tylko go używają.
1. Czym Jest Node.js i Dlaczego Jest Inny
To pytanie otwierające padające na większości rozmów. Rekruter sprawdza, czy rozumiesz fundamentalną różnicę między Node.js a tradycyjnymi serwerami.
Odpowiedź w 30 sekund
Gdy rekruter pyta "Czym jest Node.js?", odpowiadam tak:
Node.js to środowisko wykonawcze JavaScript poza przeglądarką, oparte na silniku V8 z Chrome. Kluczowa różnica w porównaniu z tradycyjnymi serwerami jak Apache to model I/O - Node jest jednowątkowy i asynchroniczny. Zamiast tworzyć nowy wątek dla każdego żądania, używa event loop do obsługi wielu operacji jednocześnie. Dzięki temu może obsłużyć tysiące połączeń przy niskim zużyciu pamięci.
Poczekaj na reakcję. Jeśli rekruter chce więcej, rozwiń.
Odpowiedź w 2 minuty
Tradycyjne serwery jak Apache działają w modelu wielowątkowym - każde żądanie dostaje własny wątek. To intuicyjne, ale kosztowne - każdy wątek zużywa pamięć, a przy tysiącach połączeń serwer może się zapchać.
Node.js podchodzi do tego inaczej. Jest jednowątkowy, ale operacje I/O (odczyt plików, zapytania do bazy, żądania sieciowe) są wykonywane asynchronicznie w tle. Gdy operacja się kończy, callback trafia do kolejki i jest wykonywany przez event loop.
Pokażę na przykładzie:
const http = require('http');
const fs = require('fs');
const server = http.createServer((req, res) => {
// Ta operacja NIE blokuje serwera
fs.readFile('duzy-plik.txt', (err, data) => {
if (err) {
res.statusCode = 500;
res.end('Błąd serwera');
return;
}
res.end(data);
});
// Serwer może obsługiwać inne żądania
// podczas gdy plik jest czytany w tle
});
server.listen(3000);
Podczas gdy jeden użytkownik czeka na odczyt pliku, serwer może obsługiwać setki innych żądań. To właśnie sprawia, że Node.js jest idealny do aplikacji I/O-intensive jak API, chat, streaming.
Kiedy Node.js NIE Jest Dobrym Wyborem
Kandydaci, którzy robią dobre wrażenie, sami wspominają o ograniczeniach:
Node.js nie nadaje się do zadań CPU-intensive - intensywne obliczenia blokują event loop i cały serwer staje. Do takich zadań lepszy jest Python, Go lub worker threads w Node. Dlatego Node świetnie sprawdza się w API i real-time apps, ale nie w przetwarzaniu obrazów czy machine learning.
2. Event Loop - Serce Node.js
To temat, który odróżnia juniorów od mid-level developerów. Każdy używa async/await, ale niewielu rozumie, co dzieje się pod spodem.
Odpowiedź w 30 sekund
Event loop to mechanizm zarządzający wykonaniem kodu w Node.js. Działa w cyklu: najpierw wykonuje cały kod synchroniczny, potem sprawdza kolejkę microtasks (Promise callbacks), następnie bierze jedno zadanie z kolejki macrotasks (setTimeout, I/O callbacks). Microtasks mają wyższy priorytet - wszystkie są wykonywane przed następnym macrotask.
Fazy Event Loop
Event loop przechodzi przez kilka faz w określonej kolejności:
┌───────────────────────────┐
┌─>│ timers │ <- setTimeout, setInterval
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ pending callbacks │ <- I/O callbacks z poprzedniej iteracji
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │ <- wewnętrzne operacje
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ poll │ <- pobieranie nowych I/O events
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ check │ <- setImmediate callbacks
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
└──┤ close callbacks │ <- socket.on('close')
└───────────────────────────┘
Klasyczny Problem: Kolejność Wykonania
Rekruter pokazuje kod i pyta o kolejność wypisania:
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
process.nextTick(() => console.log('4'));
console.log('5');
Odpowiedź: 1, 5, 4, 3, 2
Wyjaśnienie krok po kroku:
// 1. Kod synchroniczny wykonuje się pierwszy
console.log('1'); // Wypisuje: 1
console.log('5'); // Wypisuje: 5
// 2. process.nextTick ma najwyższy priorytet wśród asynchronicznych
// Wykonuje się PRZED microtasks
process.nextTick(() => console.log('4')); // Wypisuje: 4
// 3. Promise.then to microtask - wykonuje się przed macrotasks
Promise.resolve().then(() => console.log('3')); // Wypisuje: 3
// 4. setTimeout to macrotask - wykonuje się na końcu
setTimeout(() => console.log('2'), 0); // Wypisuje: 2
Priorytet: synchroniczny kod > process.nextTick > microtasks (Promise) > macrotasks (setTimeout)
Dlaczego setTimeout 0 Nie Jest Natychmiastowy
console.log('Start');
setTimeout(() => {
console.log('Timeout');
}, 0);
console.log('End');
// Wynik:
// Start
// End
// Timeout
Nawet z czasem 0, callback setTimeout trafia do kolejki macrotasks i czeka, aż cały kod synchroniczny się wykona. To fundamentalna zasada event loop - nigdy nie przerywamy wykonywania synchronicznego kodu.
3. Callback, Promise, Async/Await - Ewolucja Asynchroniczności
Na rozmowach często padają pytania o różnice między tymi trzema podejściami i kiedy którego używać.
Odpowiedź w 30 sekund
Callbacki to pierwotny sposób obsługi asynchroniczności - funkcja przekazywana jako argument, wywoływana po zakończeniu operacji. Problem: callback hell przy zagnieżdżaniu. Promise to obiekt reprezentujący przyszły wynik - może być pending, fulfilled lub rejected. Umożliwia łańcuchowanie przez .then(). Async/await to składnia na Promise - pozwala pisać kod asynchroniczny jak synchroniczny, z try/catch do obsługi błędów.
Callback Hell - Problem
// Tak wygląda "callback hell" - piramida zagłady
fs.readFile('config.json', (err, config) => {
if (err) return handleError(err);
db.connect(config, (err, connection) => {
if (err) return handleError(err);
connection.query('SELECT * FROM users', (err, users) => {
if (err) return handleError(err);
users.forEach(user => {
sendEmail(user.email, (err, result) => {
if (err) return handleError(err);
console.log('Email wysłany do', user.email);
});
});
});
});
});
Problemy: trudne do czytania, obsługa błędów w każdym poziomie, ciężkie do debugowania.
Promise - Rozwiązanie
// Ten sam kod z Promise - płaski i czytelny
const fsPromises = require('fs').promises;
fsPromises.readFile('config.json')
.then(config => db.connect(config))
.then(connection => connection.query('SELECT * FROM users'))
.then(users => {
return Promise.all(
users.map(user => sendEmail(user.email))
);
})
.then(results => {
console.log('Wszystkie emaile wysłane');
})
.catch(err => {
// Jeden punkt obsługi wszystkich błędów
console.error('Błąd:', err);
});
Async/Await - Najczystsza Składnia
async function processUsers() {
try {
const config = await fsPromises.readFile('config.json');
const connection = await db.connect(config);
const users = await connection.query('SELECT * FROM users');
// Równoległe wysyłanie emaili
await Promise.all(
users.map(user => sendEmail(user.email))
);
console.log('Wszystkie emaile wysłane');
} catch (err) {
console.error('Błąd:', err);
}
}
Klasyczny Problem: Sekwencyjne vs Równoległe
// ZŁE - sekwencyjne, wolne
async function slow() {
const user = await fetchUser(1); // czeka 1s
const posts = await fetchPosts(1); // czeka kolejną 1s
const comments = await fetchComments(1); // czeka kolejną 1s
return { user, posts, comments }; // łącznie 3s
}
// DOBRE - równoległe, szybkie
async function fast() {
const [user, posts, comments] = await Promise.all([
fetchUser(1),
fetchPosts(1),
fetchComments(1)
]);
return { user, posts, comments }; // łącznie 1s
}
Kandydaci, którzy od razu wspominają o Promise.all dla niezależnych operacji, robią bardzo dobre wrażenie.
4. Express.js i Middleware
Express to de facto standard dla API w Node.js. Rekruterzy sprawdzają, czy rozumiesz architekturę middleware.
Odpowiedź w 30 sekund
Middleware w Express to funkcje z dostępem do obiektów request, response i funkcji next(). Wykonują się w kolejności rejestracji, tworząc łańcuch. Każdy middleware może: zmodyfikować req/res, zakończyć żądanie przez res.send(), lub przekazać kontrolę dalej przez next(). Typowe zastosowania to logowanie, parsowanie body, uwierzytelnianie, obsługa błędów.
Anatomia Middleware
const express = require('express');
const app = express();
// Middleware ma dostęp do req, res i next
function loggerMiddleware(req, res, next) {
console.log(`${new Date().toISOString()} ${req.method} ${req.url}`);
next(); // Przekaż kontrolę do następnego middleware
}
// Middleware może modyfikować request
function authMiddleware(req, res, next) {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ error: 'Brak tokenu' });
// Nie wywołujemy next() - łańcuch się kończy
}
try {
const user = verifyToken(token);
req.user = user; // Dodajemy dane do request
next();
} catch (err) {
res.status(401).json({ error: 'Nieprawidłowy token' });
}
}
// Kolejność rejestracji MA znaczenie
app.use(loggerMiddleware); // Każde żądanie
app.use(express.json()); // Parsowanie JSON body
app.use('/api', authMiddleware); // Tylko dla /api/*
app.get('/api/users', (req, res) => {
// req.user jest dostępny dzięki authMiddleware
res.json({ user: req.user });
});
app.listen(3000);
Middleware Obsługi Błędów
Middleware błędów ma specjalną sygnaturę - 4 argumenty:
// Zwykły middleware
app.use((req, res, next) => {
// 3 argumenty
});
// Middleware błędów - MUSI mieć 4 argumenty
app.use((err, req, res, next) => {
console.error('Błąd:', err.stack);
// Nie ujawniaj szczegółów w produkcji
const message = process.env.NODE_ENV === 'production'
? 'Błąd serwera'
: err.message;
res.status(err.status || 500).json({
error: message
});
});
// Jak przekazać błąd do error middleware
app.get('/api/data', async (req, res, next) => {
try {
const data = await fetchData();
res.json(data);
} catch (err) {
next(err); // Przekaż do error middleware
}
});
Struktura Skalowalnej Aplikacji Express
/project
/src
/controllers
userController.js
postController.js
/routes
userRoutes.js
postRoutes.js
index.js
/middlewares
auth.js
errorHandler.js
validate.js
/models
User.js
Post.js
/services
emailService.js
paymentService.js
/utils
logger.js
app.js
/tests
package.json
// routes/userRoutes.js
const express = require('express');
const router = express.Router();
const userController = require('../controllers/userController');
const auth = require('../middlewares/auth');
router.get('/', userController.getAll);
router.get('/:id', userController.getById);
router.post('/', auth, userController.create);
router.put('/:id', auth, userController.update);
router.delete('/:id', auth, userController.delete);
module.exports = router;
// app.js
const userRoutes = require('./routes/userRoutes');
app.use('/api/users', userRoutes);
5. Streams - Przetwarzanie Dużych Danych
Streams to często pomijany temat, ale na rozmowach na stanowiska mid i senior pojawia się regularnie.
Odpowiedź w 30 sekund
Streams to abstrakcja do przetwarzania danych w kawałkach, bez wczytywania całości do pamięci. Są cztery typy: Readable (odczyt), Writable (zapis), Duplex (oba kierunki), Transform (modyfikacja podczas przepływu). Kluczowy mechanizm to backpressure - automatyczne wstrzymywanie producenta gdy konsument nie nadąża. Używamy ich przy dużych plikach, streamowaniu video, przetwarzaniu logów.
Problem Bez Streams
// ZŁE - wczytuje cały plik do pamięci
const fs = require('fs');
app.get('/video', (req, res) => {
fs.readFile('film-2gb.mp4', (err, data) => {
if (err) return res.status(500).send('Błąd');
res.send(data);
});
// Przy 100 użytkownikach = 200GB w pamięci!
});
Rozwiązanie ze Streams
// DOBRE - streamuje plik kawałek po kawałku
const fs = require('fs');
app.get('/video', (req, res) => {
const stream = fs.createReadStream('film-2gb.mp4');
// Automatycznie obsługuje backpressure
stream.pipe(res);
stream.on('error', (err) => {
console.error('Błąd streamu:', err);
res.status(500).send('Błąd');
});
});
// Zużywa tylko ~64KB pamięci niezależnie od rozmiaru pliku
Transform Stream - Przetwarzanie w Locie
const { Transform } = require('stream');
const fs = require('fs');
// Transform stream do konwersji na uppercase
const upperCaseTransform = new Transform({
transform(chunk, encoding, callback) {
// chunk to kawałek danych (Buffer)
const upperCased = chunk.toString().toUpperCase();
callback(null, upperCased);
}
});
// Czytaj plik -> przekształć -> zapisz
fs.createReadStream('input.txt')
.pipe(upperCaseTransform)
.pipe(fs.createWriteStream('output.txt'))
.on('finish', () => console.log('Gotowe!'));
Backpressure - Kontrola Przepływu
const fs = require('fs');
const readable = fs.createReadStream('duzy-plik.txt');
const writable = fs.createWriteStream('kopia.txt');
readable.on('data', (chunk) => {
// write() zwraca false gdy bufor jest pełny
const canContinue = writable.write(chunk);
if (!canContinue) {
// Wstrzymaj czytanie - konsument nie nadąża
readable.pause();
// Wznów gdy bufor się opróżni
writable.once('drain', () => {
readable.resume();
});
}
});
// Lub prościej - pipe() obsługuje to automatycznie
fs.createReadStream('duzy-plik.txt')
.pipe(fs.createWriteStream('kopia.txt'));
6. Bezpieczeństwo Aplikacji Node.js
Na stanowiskach mid i senior pytania o bezpieczeństwo są standardem. Rekruterzy sprawdzają świadomość zagrożeń.
Odpowiedź w 30 sekund
Kluczowe zagrożenia dla Node.js to: injection (SQL, NoSQL, command), XSS przy renderowaniu HTML, CSRF przy formularzach, denial of service przez blokowanie event loop. Zabezpieczenia: walidacja i sanityzacja danych wejściowych, Helmet.js dla nagłówków HTTP, tokeny CSRF, rate limiting, parametryzowane zapytania, regularne audyty zależności przez npm audit.
SQL/NoSQL Injection
// ZŁE - podatne na injection
app.get('/user', async (req, res) => {
const { email } = req.query;
// Atakujący może wysłać: email=' OR '1'='1
const query = `SELECT * FROM users WHERE email = '${email}'`;
const result = await db.query(query);
res.json(result);
});
// DOBRE - parametryzowane zapytanie
app.get('/user', async (req, res) => {
const { email } = req.query;
// Parametr jest automatycznie escapowany
const query = 'SELECT * FROM users WHERE email = $1';
const result = await db.query(query, [email]);
res.json(result);
});
// Mongoose - też bezpieczne
const user = await User.findOne({ email: req.query.email });
Helmet.js - Nagłówki Bezpieczeństwa
const helmet = require('helmet');
const express = require('express');
const app = express();
// Helmet ustawia wiele nagłówków bezpieczeństwa
app.use(helmet());
// Lub selektywnie
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
}
}));
app.use(helmet.xssFilter()); // X-XSS-Protection
app.use(helmet.noSniff()); // X-Content-Type-Options
app.use(helmet.frameguard()); // X-Frame-Options
app.use(helmet.hsts()); // Strict-Transport-Security
Rate Limiting
const rateLimit = require('express-rate-limit');
// Ogólny limit
const generalLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minut
max: 100, // max 100 żądań na IP
message: { error: 'Zbyt wiele żądań, spróbuj później' }
});
// Strict limit dla logowania
const loginLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1 godzina
max: 5, // max 5 prób
message: { error: 'Zbyt wiele prób logowania' }
});
app.use(generalLimiter);
app.use('/api/login', loginLimiter);
Walidacja Danych Wejściowych
const Joi = require('joi');
// Definiuj schemat walidacji
const userSchema = Joi.object({
email: Joi.string().email().required(),
password: Joi.string().min(8).max(128).required(),
age: Joi.number().integer().min(18).max(120)
});
// Middleware walidacji
function validateBody(schema) {
return (req, res, next) => {
const { error, value } = schema.validate(req.body);
if (error) {
return res.status(400).json({
error: 'Błąd walidacji',
details: error.details.map(d => d.message)
});
}
req.body = value; // Używaj zwalidowanych danych
next();
};
}
app.post('/api/users', validateBody(userSchema), async (req, res) => {
// req.body jest zwalidowane i bezpieczne
const user = await User.create(req.body);
res.status(201).json(user);
});
7. Testowanie Aplikacji Node.js
Pytania o testowanie pojawiają się na prawie każdej rozmowie. Rekruterzy chcą wiedzieć, czy piszesz testy i jak.
Odpowiedź w 30 sekund
Do testowania Node.js używam Jest lub Mocha z Chai. Testy jednostkowe sprawdzają pojedyncze funkcje w izolacji, mockując zależności przez Sinon lub jest.mock(). Testy integracyjne sprawdzają współpracę modułów, np. API z bazą. Do testów HTTP używam Supertest. Testy E2E sprawdzają pełny flow aplikacji.
Struktura Testu Jednostkowego
// userService.js
class UserService {
constructor(userRepository, emailService) {
this.userRepository = userRepository;
this.emailService = emailService;
}
async createUser(userData) {
const user = await this.userRepository.create(userData);
await this.emailService.sendWelcome(user.email);
return user;
}
}
// userService.test.js
const { jest } = require('@jest/globals');
const UserService = require('./userService');
describe('UserService', () => {
let userService;
let mockUserRepository;
let mockEmailService;
beforeEach(() => {
// Mockujemy zależności
mockUserRepository = {
create: jest.fn()
};
mockEmailService = {
sendWelcome: jest.fn()
};
userService = new UserService(mockUserRepository, mockEmailService);
});
describe('createUser', () => {
it('powinno utworzyć użytkownika i wysłać email powitalny', async () => {
// Arrange
const userData = { email: 'test@example.com', name: 'Test' };
const createdUser = { id: 1, ...userData };
mockUserRepository.create.mockResolvedValue(createdUser);
mockEmailService.sendWelcome.mockResolvedValue(true);
// Act
const result = await userService.createUser(userData);
// Assert
expect(mockUserRepository.create).toHaveBeenCalledWith(userData);
expect(mockEmailService.sendWelcome).toHaveBeenCalledWith('test@example.com');
expect(result).toEqual(createdUser);
});
it('powinno rzucić błąd gdy zapis się nie powiedzie', async () => {
// Arrange
mockUserRepository.create.mockRejectedValue(new Error('DB Error'));
// Act & Assert
await expect(userService.createUser({}))
.rejects.toThrow('DB Error');
});
});
});
Testy Integracyjne z Supertest
const request = require('supertest');
const app = require('./app');
const db = require('./db');
describe('User API', () => {
beforeAll(async () => {
await db.connect();
});
afterAll(async () => {
await db.disconnect();
});
beforeEach(async () => {
await db.clear();
});
describe('POST /api/users', () => {
it('powinno utworzyć nowego użytkownika', async () => {
const response = await request(app)
.post('/api/users')
.send({
email: 'test@example.com',
password: 'haslo123',
name: 'Test User'
})
.expect('Content-Type', /json/)
.expect(201);
expect(response.body).toHaveProperty('id');
expect(response.body.email).toBe('test@example.com');
expect(response.body).not.toHaveProperty('password');
});
it('powinno zwrócić 400 dla nieprawidłowego emaila', async () => {
const response = await request(app)
.post('/api/users')
.send({
email: 'nie-email',
password: 'haslo123'
})
.expect(400);
expect(response.body).toHaveProperty('error');
});
});
});
8. Skalowanie i Wydajność
Na stanowiskach senior pytania o skalowanie są obowiązkowe. Rekruterzy sprawdzają doświadczenie z produkcyjnymi systemami.
Odpowiedź w 30 sekund
Node.js skalujemy przez: klastrowanie (moduł cluster lub PM2) do wykorzystania wielu rdzeni CPU, load balancing przez Nginx lub AWS ELB, cache (Redis) dla częstych danych, connection pooling do baz danych. Monitoruję wydajność przez APM (New Relic, Datadog) i profiluję przez Chrome DevTools z flagą --inspect.
Klastrowanie z Modułem Cluster
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
console.log(`Master ${process.pid} uruchomiony`);
// Fork workers - jeden na każdy rdzeń CPU
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`Worker ${worker.process.pid} zakończył pracę`);
// Automatyczny restart
cluster.fork();
});
} else {
// Workers współdzielą port
const app = require('./app');
app.listen(3000);
console.log(`Worker ${process.pid} uruchomiony`);
}
PM2 - Prostsze Klastrowanie
# Uruchom z automatycznym klastrowaniem
pm2 start app.js -i max # max = liczba rdzeni CPU
# Lub w konfiguracji ecosystem.config.js
module.exports = {
apps: [{
name: 'api',
script: './app.js',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'production'
}
}]
};
Cache z Redis
const Redis = require('ioredis');
const redis = new Redis();
// Middleware cache'ujący
function cacheMiddleware(duration) {
return async (req, res, next) => {
const key = `cache:${req.originalUrl}`;
try {
const cached = await redis.get(key);
if (cached) {
return res.json(JSON.parse(cached));
}
// Przechwytujemy res.json aby zapisać do cache
const originalJson = res.json.bind(res);
res.json = async (data) => {
await redis.setex(key, duration, JSON.stringify(data));
return originalJson(data);
};
next();
} catch (err) {
next(); // Przy błędzie cache - kontynuuj bez cache
}
};
}
// Użycie
app.get('/api/products', cacheMiddleware(300), async (req, res) => {
const products = await Product.find();
res.json(products);
});
Na Co Rekruterzy Naprawdę Zwracają Uwagę
Po przeprowadzeniu wielu rozmów rekrutacyjnych na stanowiska Node.js developer, mogę powiedzieć, że sprawdzam następujące rzeczy:
Czy kandydat rozumie event loop. To fundament Node.js. Jeśli ktoś nie potrafi wyjaśnić kolejności wykonania setTimeout vs Promise.then, to znak, że używa Node.js bez zrozumienia.
Czy kandydat myśli o wydajności. Wspomnienie o blokowaniu event loop, streamach zamiast wczytywania całych plików, Promise.all dla równoległych operacji - to pokazuje praktyczne doświadczenie.
Czy kandydat zna zagrożenia bezpieczeństwa. Na stanowiskach mid i senior pytania o injection, XSS, rate limiting są standardem. Brak świadomości tych zagrożeń to czerwona flaga.
Czy kandydat pisze testy. Pytam o podejście do testowania, ulubione narzędzia, różnicę między testami jednostkowymi a integracyjnymi. Brak odpowiedzi sugeruje brak doświadczenia produkcyjnego.
Czy kandydat zna ograniczenia Node.js. Każda technologia ma słabe strony. Kandydat, który wie kiedy NIE używać Node.js, pokazuje dojrzałość techniczną.
Praktyka na Koniec
Zanim pójdziesz na rozmowę, upewnij się, że potrafisz odpowiedzieć na te pytania:
- Napisz prostą implementację rate limitera bez zewnętrznych bibliotek.
- Wyjaśnij, dlaczego ten kod może być problemem:
app.get('/api/hash', (req, res) => {
const hash = crypto.pbkdf2Sync(req.query.password, 'salt', 100000, 64, 'sha512');
res.json({ hash: hash.toString('hex') });
});
- Jak zaimplementowałbyś graceful shutdown serwera Express?
- Jaka jest różnica między process.nextTick() a setImmediate()?
- Jak obsłużysz nieobsłużone rejection w Promise (unhandled rejection)?
- Napisz middleware, który loguje czas wykonania każdego żądania.
Jeśli potrafisz na nie odpowiedzieć bez wahania, jesteś gotowy na większość rozmów z Node.js.
Zobacz też
- Wzorce i Architektura Backend - Java, Spring, Node.js, Express - wzorce projektowe i architektura backendu
- 15 Najtrudniejszych Pytań z JavaScript - JavaScript to fundament Node.js
- SQL na Rozmowie Rekrutacyjnej - bazy danych w aplikacjach backendowych
Chcesz Więcej Pytań z Node.js?
Ten artykuł to wierzchołek góry lodowej. Mamy ponad 100 pytań z Node.js z szczegółowymi odpowiedziami, przykładami kodu i wyjaśnieniami - od podstaw przez Express i bazy danych po microservices i deployment.
Sprawdź Pełny Zestaw Pytań Node.js →
Lub wypróbuj nasz darmowy podgląd pytań Node.js, żeby zobaczyć więcej pytań w tym stylu.
Napisane przez zespół Flipcards, na podstawie ponad 15 lat doświadczenia w branży IT i setek przeprowadzonych rozmów rekrutacyjnych w firmach takich jak BNY Mellon, UBS i wiodących firmach fintech.
Chcesz więcej pytań rekrutacyjnych?
To tylko jeden temat z naszego kompletnego przewodnika po rozmowach rekrutacyjnych. Uzyskaj dostęp do 800+ pytań z 13 technologii.
