Strona główna / Artykuły / Siedem zadań programistycznych dla początkujących z Node.js i Express, rozwiązanych i wyjaśnionych

Siedem zadań programistycznych dla początkujących z Node.js i Express, rozwiązanych i wyjaśnionych

Przepracuj siedem typowych zadań z rozmów rekrutacyjnych dla początkujących programistów Node.js, od tras Express i middleware po obsługę błędów, odwracanie ciągów znaków i wykrywanie duplikatów, oraz naucz się tłumaczyć każde z nich.

1640 słów

Podczas rozmów kwalifikacyjnych z osobami początkującymi w Node.js i Express rzadko chodzi o skomplikowane algorytmy. Zazwyczaj dostajesz proste zadania, takie jak utworzenie trasy, napisanie middleware’a lub oczyszczenie tablicy, podczas gdy rozmówca obserwuje, jak rozumujesz i czy potrafisz wyjaśnić, dlaczego twój kod działa. Siedem poniższych zadań obejmuje najczęściej występujące tematy, każde z nich zawiera działające rozwiązanie, opis kluczowych linijek kodu oraz pytania dodatkowe, które pokazują prawdziwe zrozumienie tematu.

Zadanie 1: Stwórz podstawową trasę GET

Klasyczne ćwiczenie wprowadzające: „Utwórz punkt dostępu GET pod adresem /, który odpowiada tekstem Hello World”. Rozwiązanie polega na utworzeniu aplikacji Express, zarejestrowaniu obsługi dla ścieżki korzeniowej i rozpoczęciu odsłuchiwania na określonym porcie.

import express from "express";

const app = express();

app.get("/", (req, res) => {
    res.status(200).send("Hello World");
});

app.listen(3000, () => {
    console.log("Server running on port 3000");
});

Ponieważ używa się import, projekt wymaga wpisu "type": "module" w pliku package.json lub rozszerzenia .mjs.

Czytanie linii trasy po kolei

Wezwanie app.get() rejestruje obsługę, która reaguje wyłącznie na żądania typu GET:

app.get("/", (req, res) => {

Pierwszy argument, /, to ścieżka. Funkcja zwrotna otrzymuje req, który opisuje przychodzące żądanie, oraz res, używany do tworzenia odpowiedzi.

Sama odpowiedź jest generowana tutaj:

res.status(200).send("Hello World");

status(200) ustawia kod stanu (200 jest wartością domyślną, ale jasne określenie jest lepiej czytelne), a send() zapisuje treść i kończy odpowiedź.

Na koniec uruchamiany jest serwer:

app.listen(3000, () => {

To powiązuje aplikację z portem 3000; funkcja callback jest wywoływana po tym, jak serwer przyjmuje połączenia. Prawdziwe usługi zazwyczaj odczytują numer portu z process.env.PORT.

Jak to ująć

"app.get() rejestruje ścieżkę dla żądań typu GET. Gdy klient wywoła /, Express uruchamia funkcję callback, która odpowiada ze statusem 200 i tekstem Hello World."

Zadanie 2: Napisz middleware do logowania żądań

Kolejne polecenie brzmi: ">Stwórz middleware, który loguje każde przychodzące żądanie." Middleware to funkcja o sygnaturze (req, res, next). Ta konkretna funkcja wyświetla metodę i adres URL, a następnie przekazuje kontrolę dalej; app.use() stosuje ją do wszystkich zarejestrowanych po niej ścieżek.

const logger = (req, res, next) => {
    console.log(`${req.method} ${req.url}`);
    next();
};

app.use(logger);

Każde żądanie przechodzi kolejno przez łańcuch przed tym, jak obsługa ścieżki udzieli odpowiedzi:

Client
  ↓
Request
  ↓
Logger Middleware
  ↓
Route Handler
  ↓
Response

Najważniejszym wierszem jest ostatni wewnątrz funkcji:

next();

next() informuje Express, że ten krok został zakończony i powinien zostać uruchomiony następny middleware lub ścieżka. Jeśli o to zapomnieć, żądanie pozostaje w zawieszeniu aż klient wygaśnie – to częste pytanie. Alternatywą jest zakończenie żądania poprzez wysłanie odpowiedzi, tak jak robi to middleware autoryzacyjny, gdy odrzuca brakujący token.

Jak to ująć

"Middleware otrzymuje żądanie, odpowiedź oraz funkcję next. Może sprawdzić lub zmodyfikować żądanie, a następnie albo odpowiedzieć, albo wywołać next(), aby przekazać kontrolę dalej."

Zadanie 3: Obsługa pracy asynchronicznej w ścieżce

Prawdziwe punkty końcowe czekają na dane z baz danych i innych API, więc można się spodziewać pytania „Jak radzić sobie z operacją asynchroniczną w Express?” Solidną odpowiedzią jest obsługa typu async z blokiem try/catch:

app.get("/users", async (req, res, next) => {
    try {
        const users = await getUsers();
        res.status(200).json(users);
    } catch (error) {
        next(error);
    }
});

Kluczowym elementem jest instrukcja await:

const users = await getUsers();

Zatrzymuje ona działanie tej obsługi, dopóki getUsers() nie zakończy się pomyślnie, bez blokowania innych żądań. Jeśli obietnica zostanie odrzucona, uruchamia się blok catch:

catch (error) {
    next(error);
}

next(error) przekazuje błąd do middleware obsługującego błędy z Zadania 5. Warto dodać, że Express 4 nie przechwytuje odrzuconych obietnic z obsług asynchronicznych, więc tam blok try/catch jest niezbędny, podczas gdy Express 5 przekazuje je automatycznie. Aby dowiedzieć się więcej, zapoznaj się z budową obsługi błędów na poziomie produkcyjnym w aplikacjach Node.js.

Zadanie 4: Narysuj punkty końcowe CRUD

CRUD oznacza Create, Read, Update i Delete – cztery podstawowe operacje na danych, z których każda odpowiada określonej metodzie HTTP. Przykłady punktów końcowych dla zasobu users:

app.post("/users", (req, res) => {
    res.status(201).send("User created");
});

app.get("/users", (req, res) => {
    res.status(200).send("Users");
});
app.put("/users/:id", (req, res) => {
    res.status(200).send("User updated");
});
app.delete("/users/:id", (req, res) => {
    res.status(200).send("User deleted");
});

Proces tworzenia zwraca 201 Created, a trasy dotyczące pojedynczego użytkownika przyjmują parametr :id pobrany z req.params.id. Zamiast zapamiętywać konkretne linie kodu, należy znać znaczenie każdej metody:

POST   → Create
GET    → Read
PUT    → Update
DELETE → Delete

Bądź przygotowany na różnicę między PUT a PATCH: PUT tradycyjnie zastępuje cały zasób, natomiast PATCH wprowadza tylko częściową zmianę.

Jak to ująć

"CRUD obejmuje cztery podstawowe operacje na danych. W REST POST służy do tworzenia, GET do odczytu, PUT lub PATCH do aktualizacji, a DELETE do usuwania."

Zadanie 5: Centralizacja obsługi błędów

Obsługa błędów zazwyczaj następuje po Zadaniu 3. Zacznij od trasy, która zawodzi i przekazuje błąd:

app.get("/", (req, res, next) => {
    try {
        throw new Error("Something went wrong");
    } catch (error) {
        next(error);
    }
});

Następnie dodaj obsługę błędów, która zapisuje informacje o błędzie i zwraca ogólny kod 500:

app.use((err, req, res, next) => {
    console.error(err.message);
    res.status(500).json({
        message: "Internal Server Error"
    });
});

To, co czyni tę funkcję wyjątkową, to jej sygnatura:

(err, req, res, next)

Express rozpoznaje obsługi błędów po czterech zadeklarowanych parametrach, w kolejności err, req, res, next; są one wykonywane tylko wtedy, gdy przekazany został błąd. Zarejestruj obsługę po swoich trasach i unikaj wysyłania err.message do klientów w środowisku produkcyjnym. Przepływ działania:

Route
  ↓
Error occurs
  ↓
catch(error)
  ↓
next(error)
  ↓
Error middleware
  ↓
500 Response

Czy wewnątrz obsługi błędów konieczne jest użycie next()?

Nie, jeśli obsługa sama wysyła odpowiedź:

app.use((err, req, res, next) => {
    console.error(err.message);
    res.status(500).send("Internal Server Error");
});

next musi nadal być zadeklarowany, ponieważ przy trzech parametrach Express traktuje funkcję jako zwykły middleware.

Zadanie 6: Odwrócenie ciągu znaków

Część zadań polega po prostu na użyciu JavaScripta: „Odwróć ciąg hello”. Idiomaticzne rozwiązanie jednolinijowe łączy trzy metody:

const str = "hello";
const reversed = str.split("").reverse().join("");
console.log(reversed);

Wydrukuje ono:

olleh

Co robi każdy krok

Funkcja split daje jako wynik znaki:

str.split("")

Wynikiem jest tablica:

["h", "e", "l", "l", "o"]

Następnie kolejność elementów jest odwracana na miejscu:

.reverse()

To daje:

["o", "l", "l", "e", "h"]

Na koniec znaki są łączone bez żadnego separatora:

.join("")

W ten sposób powstaje odwrócony ciąg:

"olleh"

Powszechna modyfikacja zadania: „Teraz zrób to bez użycia reverse()”. Pętla przechodzi od ostatniego indeksu w dół do zera:

const str = "hello";
let reversed = "";
for (let i = str.length - 1; i >= 0; i--) {
    reversed += str[i];
}
console.log(reversed);

Oba podejścia są liniowe. Warto zaznaczyć, że split("") oraz indeksowanie działają na jednostkach kodu UTF-16, więc emoji są rozdzielane; natomiast [...str] dzieli ciąg według punktów kodu.

Zadanie 7: Znalezienie duplikatów w tablicy

Kolejny ulubiony przykład: „Zwróć wartości, które występują więcej niż raz”. Dane wejściowe:

const numbers = [1, 2, 3, 2, 4, 3, 5];

Oczekiwany wynik to:

[2, 3]

Użyj dwóch obiektów Set – jednego dla wartości już widzianych, a drugiego dla potwierdzonych duplikatów:

const numbers = [1, 2, 3, 2, 4, 3, 5];
const seen = new Set();
const duplicates = new Set();
for (const number of numbers) {
    if (seen.has(number)) {
        duplicates.add(number);
    } else {
        seen.add(number);
    }
}
console.log([...duplicates]);

Podczas uruchomienia programu pojawia się następujący log:

[2, 3]

Dlaczego dwie tablice typu Set działają skutecznie

seen rejestruje każdą przekazaną wartość. Pierwsze wystąpienie jest tam dodawane:

seen.add(number);

Ponowne wystąpienie powoduje pomyślne przejście tej sprawdzenia:

if (seen.has(number))

Następnie wartość trafia do tablicy duplicates, a ponieważ Set ignoruje powtórzenia, jest ona raportowana tylko raz. Zapytania do Set odbywają się w czasie niemal stałym, więc rozwiązanie jest liniowe, a nie oparte na kwadratowych pętlach nawzajem zawartych. Aby policzyć liczba wystąpień, użyj Map.

Rozważ swoje podejście przed zapisaniem kodu

Najważniejszym nawykiem jest wyjaśnienie swojego planu przed napisaniem kodu, ponieważ rozmówcy oceniają zarówno rozumowanie, jak i wyniki. W zadaniu z duplikatami zacznij od czegoś w rodzaju:

"Plan polega na użyciu zbioru wartości, które już zostały zauważone. Podczas pętli każda wartość, która już się w nim znajduje, trafia do drugiego zbioru duplikatów, dzięki czemu każda z nich pozostaje unikalna."

Następnie napisz kod. To dowodzi zrozumienia tematu, a nie tylko zapamiętania informacji, i pozwala rozmówcy wcześnie skierować cię w odpowiednim kierunku.

Lista kontrolna przygotowań

Przed rozmową kwalifikacyjną z juniorem w Node.js lub Express upewnij się, że potrafisz swobodnie:

  • Definiować trasy i wyjaśniać rolę req oraz res
  • Używać app.use() i pisać własne middleware
  • Wyjaśniać, co robi funkcja next() i co się dzieje, gdy jest brakująca
  • Zarządzaj pracą asynchroniczną za pomocą async/await, try/catch oraz next(error)
  • Napisz obsługę błędów z czterema parametrami i zarejestruj ją po trasach
  • Połącz operacje CRUD z metodami HTTP oraz odpowiednimi kodami stanu
  • Manipuluj ciągami znaków i tablicami za pomocą podstawowych metod JavaScript
  • Wybierz między Set a Map do wyszukiwania i liczenia
  • Opisuj swój sposób pracy podczas programowania
  • Literatura pokrewna

  • Podbijanie gatunków czasowych do rzeczywistych zadań data: siedem praktycznych przykładów — Przeanalizuj siedem codziennych zadań związanych z datami i godzinami za pomocą JavaScript Temporal, od prostych dat i przekroczeń miesięcy po strefy czasowe, arytmetykę DST oraz weryfikację danych.
  • Przygotowanie do interwju z Node.js i Express: 70 pytań z dokładnymi odpowiedziami — Ustrukturyzowana przeglądka 70 pytań z interwju dotyczących Node.js i Express, od pętli wydarzeń i strumieni po middleware, bezpieczeństwo i skalowanie, wraz z korektami powszechnie błędnych odpowiedzi.