読み込み中...

JavaScriptでサーバーサイド開発を実現!7つの基本と15種類のサンプルコード

初心者が理解できるJavaScriptサーバーサイドの解説とサンプルコード JS
この記事は約27分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

JavaScriptでサーバーサイド開発を進める結論は、Node.jsで実行環境を作り、ExpressでHTTP APIを組み、asyncawaitで非同期処理を整理することです。その流れを押さえると、初心者は最小構成から学習しやすく、上級者は認証、リアルタイム通信、クラウド連携などの応用技術へ広げやすくなります。

動作確認環境
  • Node.js v26.3.0 documentation を参照、学習用の実行例は Node.js 22 LTS 以降を想定
  • Express 5 系、npm 10 系、Google Chrome 126 以降のブラウザを想定

公式ドキュメントによれば、Node.jsはブラウザ外でJavaScriptを動かす実行環境です。MDNのJavaScript Guideは言語仕様の確認に向き、サーバーサイドの技術解説と組み合わせると、プログラミングの土台を崩さずに開発を進められます。

📖 この記事で学べること
  • JavaScriptとサーバーサイド開発の役割分担
  • Node.jsとExpressによるAPI開発の流れ
  • 認証、DB接続、リアルタイム通信のサンプルコード
  • 初心者がつまずきやすいエラーの読み方
  • 上級者が意識したい応用技術と設計観点

JavaScriptとサーバーサイド開発の基本

JavaScriptはブラウザ上の画面操作だけでなく、Node.jsを使うことでサーバーサイドの処理にも利用できます。そのため、画面側とAPI側を同じ言語で書ける構成になり、開発チーム内の学習範囲をそろえやすいという特徴があります。

これを支えるのが、イベント駆動と非同期処理です。たとえばPromiseasyncawaitを使うと、ファイル読み込み、データベース問い合わせ、外部API通信などの待ち時間を扱いやすくなるのが目安です。

一方、サーバーサイド開発では、画面表示よりもHTTPJSONCookieSessionCORS、認証、ログ、例外処理を強く意識するのが基本です。初心者がつまずきやすいのは、JavaScriptの文法そのものより、リクエストとレスポンスの流れを頭の中で追えなくなる場面です。

その流れは、ブラウザやアプリからGETPOSTが送られ、サーバーがルーティングを判定し、必要な処理を終えてstatusと本文を返す、という形で整理できます。JavaScriptイベント徹底解説!30個の使い方と応用例で扱うイベントの考え方は、サーバー側のイベント駆動にもつながりますし、ここがポイントです。

JavaScriptの基本概念

一般に、JavaScriptのサーバーサイドプログラミングでは、constletfunctionclassmodule.exportsimportexportなどを読み分けます。特にArray.prototype.mapforEachfilterfindはデータ加工で頻繁に現れるため、APIのレスポンス整形にも直結します。

このとき、配列操作の戻り値を誤解すると不具合が生まれますが、これは押さえたい点です。JavaScriptのforEachでreturnを活用する6つのテクニックforEach・mapの使い分け術10選の内容は、サーバーサイドで配列を扱う前提知識として役立ちますし、ここがポイントです。

サーバーサイド開発の役割

サーバーサイドの主な役割は、業務ルールの処理、データの永続化、認証認可、外部サービス連携です。その処理をroutermiddlewarecontrollerservicerepositoryのように分けると、上級者向けの大きな開発でも見通しを保ちやすくなります。

ただし、小さな学習用アプリで最初から層を増やしすぎると、初心者には処理の入口が見えにくくなるのがポイントです。そのため、最初はapp.get()app.post()でリクエストを受け、慣れてきたらファイル分割する流れが現実的だと言えるでしょう。

JavaScriptでのサーバーサイドプログラミングの始め方

JavaScriptでサーバーサイドプログラミングを始めるには、node -vnpm -vで実行環境を確認し、プロジェクト用のディレクトリにpackage.jsonを用意します。その後、npm install expressでWebサーバー用のライブラリを追加する流れになるのが目安です。

その作業で作られるpackage.jsonには、依存パッケージ、起動コマンド、モジュール形式などがまとまりますし、これが一つの目安です。scriptsstartdevを登録すると、開発時の起動方法をチームでそろえられるでしょう。

💡 Tips: Expressのルーティング仕様はExpress公式のRoutingガイドで確認できます。学習中はサンプルコードを丸暗記するより、methodpathhandlerの対応関係を見るほうが理解しやすいです。

環境構築の流れ

具体的には、任意のフォルダを作成し、npm init -yで初期設定を作ります。これによりnameversionmainlicenseなどの項目が入り、依存関係を管理する準備が整いるのが一般的です。

その後、npm install expressを実行し、server.jsapp.jsを作成するのがポイントです。JavaScriptで拡張子を活用する方法12選で扱う.js.mjsの違いも、モジュール形式を選ぶ際の判断材料になります。

最小のサーバーサイドアプリケーション

基本的な開発では、最初に小さなレスポンスを返すAPIを作りますが、覚えておくと役立つでしょう。この段階でreqresの意味、res.json()res.send()の違い、listen()でポートを開く流れを確認します。

const express = require('express');
const app = express();

app.get('/', (req, res) => {
  res.json({ message: 'Hello API' });
});

app.listen(3000, () => {
  console.log('listening on http://localhost:3000');
});

結果: 期待される表示は、http://localhost:3000/へアクセスしたときに{"message":"Hello API"}が返る状態です。

実践的なサンプルコード

サンプルコードは、読むだけでなく「どの入力に対して何を返すか」を追うと理解が深まります。ここからは、サーバーサイド開発でよく使う処理をJavaScriptのプログラミング例として扱い、初心者向けの小さな構成から上級者向けの応用技術まで広げます。

項目主な役割関連するコード注意点学習対象
ExpressHTTP APIの入口app.getルート順序初心者
JSON解析リクエスト本文の取得express.json()サイズ制限初心者
RESTリソース操作GET/POST状態コード初心者
ルートパラメータID取得req.params型変換初心者
クエリ検索条件req.query未入力対応初心者
POST登録req.body検証初心者
PUT全体更新app.put存在確認中級
PATCH部分更新app.patch差分処理中級
DELETE削除app.delete冪等性中級
MongoDB永続化mongoose.model接続管理中級
JWT認証jwt.sign秘密鍵管理中級
Middleware共通処理next()順序中級
Error例外処理try/catch情報漏えい中級
Socket.IO双方向通信io.emit接続数上級者
MQTTIoT通信client.subscribeQoS上級者
BigQuery分析createQueryJob課金上級者
環境変数設定分離process.env漏えい防止中級
ログ調査console.error個人情報中級
入力検証不正値対策Number.isInteger境界値中級
CORS別オリジン制御Access-Control-Allow-Origin許可範囲中級
Cookie状態保持Set-CookieHttpOnly中級
Sessionログイン保持session保存先中級
ファイル読み書きfs/promisesパス検証中級
ストリーム大容量処理Readableバックプレッシャー上級者
テスト品質確認node:test副作用分離中級
Docker環境統一Dockerfile秘密情報上級者
CI自動確認npm test失敗時停止上級者
レート制限過剰アクセス対策429共有IP上級者
キャッシュ応答高速化Cache-Control更新反映上級者
OpenAPI仕様共有openapi.yaml実装差分上級者

サンプルコード1:APIサーバー

これが最小のAPIサーバーです。app.get()は特定のURLに対応する処理を登録し、res.status()でHTTP状態コード、res.json()でレスポンス本文を返します。

const express = require('express');
const app = express();

app.get('/health', (req, res) => {
  res.status(200).json({ ok: true });
});

app.listen(3000);

結果: 期待される出力は、/healthへのGETリクエストで{"ok":true}が返る状態です。

サンプルコード2:POSTでデータを受け取る

その次に扱うのは、JSON本文を受け取る処理です。express.json()を通すことでreq.bodyが使えるようになり、登録系APIのサンプルコードとして応用できます。

const express = require('express');
const app = express();
app.use(express.json());

app.post('/users', (req, res) => {
  const user = { id: 1, name: req.body.name };
  res.status(201).json(user);
});

app.listen(3000);

結果: 期待される出力は、{"name":"Alice"}を送ったときに{"id":1,"name":"Alice"}が返る状態です。

サンプルコード3:RESTful API

RESTful APIでは、URLをリソース、HTTPメソッドを操作として扱います。/itemsに対するGETPOST/items/:idに対するPUTDELETEを分けると、サーバーサイドの設計が読みやすくなります。

const express = require('express');
const app = express();
app.use(express.json());
const items = [];

app.get('/items', (req, res) => res.json(items));
app.post('/items', (req, res) => {
  const item = { id: items.length + 1, name: req.body.name };
  items.push(item);
  res.status(201).json(item);
});
app.delete('/items/:id', (req, res) => {
  const id = Number(req.params.id);
  const index = items.findIndex(item => item.id === id);
  if (index === -1) return res.status(404).json({ error: 'not found' });
  items.splice(index, 1);
  res.status(204).send();
});
app.listen(3000);

結果: 期待される表示は、登録後にGET /itemsで配列が返り、存在しないIDの削除では404が返る状態です。

サンプルコード4:MongoDB接続

データを永続化する場合は、メモリ配列ではなくデータベースを使います。MongoDBとmongooseを組み合わせると、Schemamodelでデータ構造を定義できます。

const express = require('express');
const mongoose = require('mongoose');
const app = express();
app.use(express.json());

mongoose.connect(process.env.MONGO_URL || 'mongodb://127.0.0.1:27017/example');
const Item = mongoose.model('Item', new mongoose.Schema({ name: String }));

app.post('/items', async (req, res, next) => {
  try {
    const item = await Item.create({ name: req.body.name });
    res.status(201).json(item);
  } catch (error) {
    next(error);
  }
});

app.listen(3000);

結果: 期待される出力は、MongoDBが起動している環境でPOST /itemsにより登録済みドキュメントがJSONで返る状態です。

サンプルコード5:JWT認証

認証では、ユーザーが誰かを確認し、保護されたAPIへのアクセスを制御します。ただし、平文パスワードや固定の秘密鍵は学習用の簡略化に限り、開発ではbcryptprocess.env.JWT_SECRETの利用を前提にします。

const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
app.use(express.json());

const secret = process.env.JWT_SECRET || 'development-secret';

app.post('/login', (req, res) => {
  if (req.body.username !== 'demo') return res.status(401).json({ error: 'unauthorized' });
  const token = jwt.sign({ sub: 'demo' }, secret, { expiresIn: '1h' });
  res.json({ token });
});

app.get('/secure', (req, res) => {
  const header = req.headers.authorization || '';
  const token = header.replace('Bearer ', '');
  const payload = jwt.verify(token, secret);
  res.json({ user: payload.sub });
});

app.listen(3000);

結果: 期待される表示は、/loginで取得したトークンをAuthorizationヘッダーに入れると{"user":"demo"}が返る状態です。

サンプルコード6:ミドルウェア

同様に、全APIで共通する処理はミドルウェアに寄せます。ログ、認証、入力検証、エラー変換をnext()でつなぐと、個別のルートが本来の処理に集中できます。

const express = require('express');
const app = express();

app.use((req, res, next) => {
  console.log(`${req.method} ${req.path}`);
  next();
});

app.get('/ping', (req, res) => {
  res.send('pong');
});

app.listen(3000);

結果: 期待される出力は、GET /pingへアクセスしたときにレスポンス本文がpongになり、コンソールにはリクエスト行が出る状態です。

サンプルコード7:入力検証

入力検証はセキュリティとデータ品質を守る処理です。型、長さ、必須項目を確認し、問題があれば400で返す設計にすると、クライアント側も修正点を判断できます。

const express = require('express');
const app = express();
app.use(express.json());

app.post('/products', (req, res) => {
  const price = Number(req.body.price);
  if (!req.body.name || !Number.isFinite(price)) {
    return res.status(400).json({ error: 'invalid product' });
  }
  res.status(201).json({ name: req.body.name, price });
});

app.listen(3000);

結果: 期待される表示は、nameが空またはpriceが数値でない場合に{"error":"invalid product"}が返る状態です。

サンプルコード8:ファイル読み込み

設定ファイルやテンプレートを読む処理では、fs/promisesが使えます。非同期のファイル処理をawaitで扱うと、サーバーサイドのプログラミングでも同期処理のように読みやすく整理できます。

const { readFile } = require('node:fs/promises');

async function loadConfig() {
  const text = await readFile('./config.json', 'utf8');
  return JSON.parse(text);
}

loadConfig().then(config => console.log(config.name));

結果: 期待される出力は、config.json{"name":"api"}がある場合にapiがコンソールへ出る状態です。

サンプルコード9:リアルタイム通信

リアルタイム通信では、HTTPのリクエスト単位ではなく接続を保ったままメッセージを流します。Socket.IOを使うと、チャットや通知のような応用技術をJavaScriptで組み立てられます。

const express = require('express');
const http = require('node:http');
const { Server } = require('socket.io');

const app = express();
const server = http.createServer(app);
const io = new Server(server);

io.on('connection', socket => {
  socket.on('message', message => io.emit('message', message));
});

server.listen(3000);

結果: 期待される表示は、接続済みクライアントがmessageイベントを送ると、全クライアントへ同じイベントが配信される状態です。

サンプルコード10:エラーハンドリング

例外処理は、失敗を隠すためではなく、予測できる形式でクライアントへ返すために使います。Expressでは最後にエラー処理用ミドルウェアを置き、next(error)で処理を渡します。

const express = require('express');
const app = express();

app.get('/boom', (req, res, next) => {
  next(new Error('sample error'));
});

app.use((error, req, res, next) => {
  res.status(500).json({ error: 'internal server error' });
});

app.listen(3000);

結果: 期待される出力は、/boomへアクセスしたときに500{"error":"internal server error"}が返る状態です。

よくあるエラーとその対処法

JavaScriptのサーバーサイド開発で出会いやすいエラーは、構文エラー、型の不一致、非同期処理の失敗、環境変数の不足、外部サービスの接続失敗に分けられます。その分類ができると、エラーメッセージの読み方が安定します。

初心者がつまずきやすいのは、undefinednullに対してプロパティを読もうとするケースです。一方、上級者でも分散した処理の中でawait忘れや例外の握りつぶしを起こすことがあるのが現実的です。

⚠️ 注意: エラーレスポンスにスタックトレース、SQL、トークン、メールアドレスなどをそのまま含めると情報漏えいにつながりますが、これは押さえたい点です。開発中のログとクライアント向けメッセージは分けて扱うのが基本です。

TypeErrorへの対応

これに対しては、条件分岐、オプショナルチェーン、初期値の付与が有効です。user?.name??を使うと、値がない場合の分岐を短く書けます。

function displayName(user) {
  const name = user?.name ?? 'anonymous';
  return name.toUpperCase();
}

console.log(displayName({ name: 'Alice' }));
console.log(displayName(null));

結果: 期待される出力は、最初の呼び出しでALICE、次の呼び出しでANONYMOUSが表示される状態です。

非同期処理の失敗

その失敗は、Promiseの拒否を捕捉していないときに表面化します。trycatchasync functionの内側で使い、Expressではnext(error)へ渡すと、エラー処理を一箇所に集められます。

async function readUser(id) {
  if (!id) throw new Error('id is required');
  return { id, name: 'Alice' };
}

readUser(null).catch(error => {
  console.error(error.message);
});

結果: 期待される出力は、id is requiredというメッセージがエラーログとして出る状態です。

環境変数の不足

環境変数は、接続先URLや秘密鍵をコードから分離するために使います。ただし、process.envは存在しないキーでもundefinedを返すため、起動時に必須項目を検査する実装が扱いやすいです。

const required = ['MONGO_URL', 'JWT_SECRET'];
for (const key of required) {
  if (!process.env[key]) {
    throw new Error(`${key} is missing`);
  }
}

結果: 期待される出力は、必要な環境変数がない場合にMONGO_URL is missingのようなエラーで起動が止まる状態です。

JavaScriptでのサーバーサイド開発の応用例

JavaScriptのサーバーサイド開発は、APIだけで終わりません。チャット、EC、IoT、分析、クラウド連携など、応用技術を組み合わせることで、プロダクトの中核を担うバックエンドへ発展します。

一方、応用例が増えるほど、認証、監視、データ整合性、リトライ、タイムアウトの設計が欠かせなくなります。サンプルコードは入口として読み、実サービスでは設定値、秘密情報、障害時の振る舞いを追加する必要があると整理できるのが一般的です。

サンプルコード11:チャットアプリケーション

チャットでは、メッセージの送信者、本文、作成時刻を扱います。socket.idを使えば接続単位の識別ができ、ログイン機能と組み合わせるとユーザー単位の通知にも展開できます。

io.on('connection', socket => {
  socket.on('chat:send', text => {
    io.emit('chat:message', {
      id: socket.id,
      text,
      createdAt: new Date().toISOString()
    });
  });
});

結果: 期待される表示は、あるクライアントがchat:sendを送ると、全クライアントがchat:messageを受け取る状態です。

サンプルコード12:Eコマースのバックエンド

Eコマースでは、商品、注文、在庫、決済の状態がつながります。そのため、商品登録のサンプルコードでもpriceの検証や在庫数の扱いを入れると、実際の開発に近い読み方になります。

const products = [];

app.post('/products', (req, res) => {
  const price = Number(req.body.price);
  const stock = Number(req.body.stock);
  if (!req.body.name || price < 0 || stock < 0) {
    return res.status(400).json({ error: 'invalid product' });
  }
  const product = { id: products.length + 1, name: req.body.name, price, stock };
  products.push(product);
  res.status(201).json(product);
});

結果: 期待される出力は、正しい商品情報を送るとid付きの商品JSONが返り、不正な価格では400が返る状態です。

サンプルコード13:IoTデバイス管理

IoTでは、HTTP APIだけでなくMQTTのような軽量プロトコルが使われます。デバイスからの状態通知を購読し、Web APIから制御コマンドを発行する構成は、サーバーサイドの応用技術としてよく見られます。

const mqtt = require('mqtt');
const client = mqtt.connect('mqtt://broker.hivemq.com');

client.on('connect', () => {
  client.subscribe('devices/+/status');
});

app.post('/devices/:id/command', (req, res) => {
  client.publish(`devices/${req.params.id}/command`, req.body.command);
  res.status(202).json({ accepted: true });
});

結果: 期待される表示は、POST /devices/abc/commandでMQTTトピックdevices/abc/commandへ命令が送られる状態です。

サンプルコード14:BigQueryによる分析

大量データの分析では、Node.jsが集計そのものをすべて抱えるのではなく、BigQueryのような分析基盤へ問い合わせます。この分担により、JavaScript側は認証、入力検証、結果整形に集中できます。

const { BigQuery } = require('@google-cloud/bigquery');
const bigquery = new BigQuery();

app.get('/reports/orders', async (req, res, next) => {
  try {
    const [rows] = await bigquery.query({
      query: 'SELECT status, COUNT(*) AS total FROM `project.dataset.orders` GROUP BY status',
      location: 'US'
    });
    res.json(rows);
  } catch (error) {
    next(error);
  }
});

結果: 期待される出力は、適切なGoogle Cloud認証とテーブルがある場合に、注文状態ごとの件数が配列で返る状態です。

サンプルコード15:クラウドストレージ連携

クラウドストレージ連携では、画像、CSV、ログなどのファイルをAPI経由で扱います。ただし、アップロード処理ではファイルサイズ、拡張子、MIMEタイプ、保存先パスの検証を組み込む必要があります。

app.get('/files/:name', async (req, res, next) => {
  try {
    const name = req.params.name.replace(/[^a-zA-Z0-9._-]/g, '');
    const file = await storage.bucket(process.env.BUCKET_NAME).file(name).download();
    res.type('application/octet-stream').send(file[0]);
  } catch (error) {
    next(error);
  }
});

結果: 期待される表示は、クラウドストレージ設定がそろった環境で安全化したファイル名のデータがダウンロードされる状態です。

エンジニアが知っておくべき豆知識

エンジニアがJavaScriptのサーバーサイド開発を続けるうえで、言語仕様、セキュリティ、パフォーマンス、運用の知識は切り離せません。技術解説を読むときも、サンプルコードが学習用なのか、本番運用に近いのかを見分ける必要があります。

これらの観点は、初心者にとっては難しく見えるかもしれません。ただし、HTTPの状態コード、try/catch、入力検証、環境変数の管理を少しずつ加えるだけでも、コードの信頼性は大きく変わります。

JavaScriptとTypeScriptの使い分け

一般的に、小さな検証や学習ではJavaScriptだけでも進められますし、ここを基本と考えるとよいでしょう。一方、上級者が複数人で開発するAPIでは、TypeScriptinterfacetypeを定義し、リクエストとレスポンスの形を明示する構成が扱いやすいです。

その型定義は、OpenAPIやテストとも相性があるのが現実的です。API仕様、型、実装、テストのズレを減らすことは、サーバーサイドのプログラミングを長期運用へ近づける考え方です。

セキュリティで見るべき範囲

セキュリティでは、SQLインジェクション、XSS、CSRF、認証情報の漏えい、過剰な権限付与を確認します。たとえばHttpOnlySecureSameSiteをCookieに設定すると、ブラウザ経由の攻撃面を減らせますし、ここがポイントです。

ただし、Cookieだけで安全になるわけではありません。パスワードハッシュ化、レート制限、監査ログ、依存パッケージの更新、エラーメッセージの制御まで含めて考えるのが基本です。

ℹ️ 補足: ブラウザ側のイベントやイベントハンドラを理解しておくと、認証画面、入力フォーム、API呼び出しの境界が読みやすくなります。関連する基礎はJavaScriptにおけるイベントハンドラを完全ガイド!20選の実践サンプルコード付きも参考になると整理できます。

パフォーマンスと運用

パフォーマンスでは、非同期処理の並列化、データベースインデックス、キャッシュ、ストリーム処理が論点になると理解できます。Promise.all()は独立した処理をまとめて待つ場面で使えますが、外部APIに過剰な同時アクセスを送らない制御も必要です。

運用では、ログとメトリクスが問題調査の入口になります。requestIdを付け、statusCode、処理時間、ユーザー識別子を個人情報に配慮して記録すると、障害時の追跡がしやすくなるでしょう。

まとめ

JavaScriptでサーバーサイド開発を行うには、Node.jsで実行環境を用意し、Expressでルーティングを作り、JSON、認証、データベース、エラーハンドリングを順に組み合わせますし、これが一つの目安です。この流れを押さえると、初心者でもAPIの入口から理解しやすくなります。

その一方で、上級者が扱う開発では、応用技術だけでなく運用時の失敗にも備える必要があると考えられます。ログ、監視、入力検証、秘密情報管理、テストを組み込むことで、サンプルコードから実用的なサーバーサイドプログラミングへ進めますが、覚えておくと役立つでしょう。

技術解説を読む際は、コードの短さだけで判断せず、何を省略しているかを見る姿勢が欠かせません。JavaScriptの文法、HTTPの仕組み、データ保存、セキュリティをつなげて理解すると、開発の選択肢が広がります。

著者: Japanシーモア編集部

Japanシーモアは、Web/IoT/APP/SYS 分野のプログラミング情報を体系的に提供するメディアです。本記事は編集部による執筆とAI支援を組み合わせて制作し、公開前に編集部が校正しています。誤りや改善案がございましたらお問い合わせよりご連絡ください。

※本記事は実在のエンジニア複数名で構成される Japanシーモア編集部が、AI支援を活用して作成・校正・公開しています。

関連記事