TypeScriptでファイルアップロード(Multer)を型安全に実装する方法!初心者向けNode.js/Expressバックエンド開発徹底解説
生徒
「TypeScriptを使って、ウェブサイトから画像やファイルを安全にアップロードする機能をバックエンドで作ってみたいのですが、どうすれば良いですか?」
先生
「Node.jsのExpressという仕組みと、Multerというライブラリを使うことで、簡単にファイルアップロードが実装できます。さらにTypeScriptの力を借りて、中身が壊れていないか、エラーが起きないかを事前に確認する型安全な仕組みを作りましょう!」
生徒
「難しそうですが、パソコン初心者でも分かりますか?」
先生
「大丈夫です!専門用語の解説を交えながら、一歩ずつプログラムの書き方を見ていきましょう!」
1. ファイルアップロードの仕組みとMulterとは
インターネット上のウェブサイトで、自分のパソコンにある画像や書類を選択して送信ボタンを押すことがあります。これをファイルアップロードと呼びます。この時、画面の裏側で動いているシステムをバックエンドやサーバーと呼び、送信されたファイルを正しく受け取って保存する役割を持っています。
今回の開発では、Node.jsというパソコン上でJavaScriptを動かす基礎的な仕組みと、それを拡張してウェブの受付窓口を簡単に作れるExpressという仕組みを利用します。しかし、Expressだけでは送信されてきた画像ファイルなどを上手に受け取ることができません。そこで登場するのがMulterというライブラリです。ライブラリとは、特定の便利な機能を誰でも使えるようにひとまとめにした部品箱のようなものです。Multerという部品を組み込むことで、複雑なファイルの受け取り処理を数行のプログラムで安全に行うことができるようになります。
2. TypeScriptと型安全の重要性
プログラミングの基礎知識として、データには種類があります。文字のデータ、数字のデータ、画像のようなファイルのデータなどがあり、これらを型と呼びます。通常のJavaScriptという言語では、この種類が曖昧なままプログラムが進んでしまうため、数字が入るはずの場所に文字が入ってしまい、パソコンが混乱して動かなくなる原因になります。
そこで活躍するのがTypeScriptです。これは、プログラムを書く段階で「ここには必ず画像ファイルが入ります」「ここには文字が入ります」というルールを厳しく決めることができる言語です。このように、データの種類を明確にして、間違いを事前に防ぐ仕組みを型安全と呼びます。ファイルアップロードの処理では、送信されてきたデータが本当にファイルなのか、それともただの文字なのかを厳密に区別しないと、重大なシステム不具合や安全上の問題に繋がります。そのため、TypeScriptを使って型安全にMulterを動かすことが非常に大切になります。
3. 開発を始めるための事前準備とインストール
プログラムを書く前に、必要な道具をパソコンに準備する必要があります。これをインストール作業と呼びます。まずは、MulterやExpress、そしてTypeScriptを動かすための命令をパソコンに覚えさせます。コマンドプロンプトやターミナルと呼ばれる、文字を入力してパソコンを操作する画面を使って、必要な部品をダウンロードします。
この時に重要なのが、型定義ファイルというものです。Multerは元々JavaScriptで作られているため、TypeScriptで使うためには「この部品にはこういうデータが入りますよ」という説明書が追加で必要になります。これをインストールすることで、プログラムを書いている最中にパソコンが自動で間違いを指摘してくれるようになります。準備が整ったら、実際のプログラムを入力していきましょう。
4. 最もシンプルなファイル受け取りプログラム
まずは、パソコンから送信されてきたファイルを一つだけ受け取って、特定のフォルダに保存する最も基本的なプログラムを書いてみましょう。ここでは、受け付けたファイルをそのまま保存する設定を行います。プログラムの中に登場する「upload.single」という記述が、ファイルを一つだけ受け取るという意味の命令です。
import express from 'express';
import multer from 'multer';
const app = express();
const upload = multer({ dest: 'uploads/' });
app.post('/upload', upload.single('fileData'), (req, res) => {
console.log(req.file);
res.send('ファイルを保存しました');
});
app.listen(3000, () => {
console.log('サーバーが起動しました');
});
このプログラムを実行して、ウェブ画面からファイルを送信すると、パソコンの画面に受け取ったファイルの詳細な情報が表示されます。表示される結果は、以下のような形式になります。
{
fieldname: 'fileData',
originalname: 'sample.png',
mimetype: 'image/png',
destination: 'uploads/',
filename: 'abcdef123456',
path: 'uploads/abcdef123456',
size: 15420
}
この出力結果を見ることで、ファイルの名前やデータの大きさ、どこに保存されたのかという情報を、システムが自動的に解析して管理してくれていることが分かります。
5. ファイルの情報を安全に読み出す型定義の追加
先ほどの基本プログラムでは、最低限の動きを確認できましたが、TypeScriptの利点である型安全が不十分です。パソコンに対して「これから読み出すデータはMulterが処理したファイルの情報ですよ」ということを、より具体的に伝えてあげる必要があります。そうしないと、プログラムが「req.fileの中身が何かわからないので処理できません」と怒ってしまいます。
これを解決するために、Expressが提供するリクエストという仕組みに、Multerの型を明示的に指定します。これにより、ファイルの名前を変更したり、中身を取り出したりする際に、打ち間違いによる不具合を完全に防ぐことができます。以下のプログラムでは、安全にファイルの情報にアクセスする方法を記述しています。
import express, { Request, Response } from 'express';
import multer from 'multer';
const app = express();
const upload = multer({ dest: 'uploads/' });
interface CustomRequest extends Request {
file?: Express.Multer.File;
}
app.post('/secure-upload', upload.single('avatar'), (req: CustomRequest, res: Response) => {
if (!req.file) {
res.status(400).send('ファイルが選択されていません');
return;
}
const fileName = req.file.originalname;
res.send('受け取ったファイル名は ' + fileName + ' です');
});
このプログラムでは、もしファイルが送信されてこなかった場合の対策も入っています。ファイルが存在しないときは、途中で処理を中断して、画面側に「ファイルがありません」という警告メッセージを返す仕組みになっています。
6. 保存先とファイル名を細かく指定する応用設定
初期の設定では、保存されたファイルの名前が自動的にランダムな英数字に書き換わってしまい、後から確認するのが大変です。実際のシステム開発では、送信されてきた元の名前を維持したまま、日付などを付け加えて保存することが一般的です。Multerには、保存する場所と名前を細かく決めるための、ディスクストレージという便利な機能が用意されています。
この機能を使う際にも、TypeScriptの型指定が活躍します。どの場所にどんなデータが渡されてくるのかを事前に固定することで、フォルダの名前を間違えてファイルが迷子になってしまうトラブルを防ぎます。次のプログラムでは、保存先を詳細にカスタマイズする方法を示しています。
import express from 'express';
import multer from 'multer';
const app = express();
const myStorage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'my-photos/');
},
filename: (req, file, cb) => {
const uniqueSuffix = Date.now() + '-' + file.originalname;
cb(null, uniqueSuffix);
}
});
const upload = multer({ storage: myStorage });
app.post('/photo', upload.single('imageFile'), (req, res) => {
res.send('指定のフォルダに独自のファイル名で保存しました');
});
この処理を行うことで、同じ名前のファイルが同時に送信されてきても、現在の時刻を表す数字が自動的に名前に組み込まれるため、古いファイルが上書きされて消えてしまう悲しい事故を防ぐことができます。
7. 不正なファイルを除外する安全フィルターの実装
ファイルアップロード機能を作る上で最も気をつけなければいけないのが、安全対策です。画像を受け取るつもりが、パソコンを破壊するような危険なプログラムファイルを送信されてしまうと、システム全体が乗っ取られる危険性があります。そのため、送信されてきたファイルの種類を厳しくチェックするフィルター機能が必要です。
Multerのファイルフィルターという仕組みを使い、ファイルの拡張子やデータの種類を判別します。例えば、写真データである「png」や「jpeg」の形式だけを許可し、それ以外の怪しいファイルは受け取りを拒否する命令を出します。これもTypeScriptで記述することで、安全点検のルールを強固に構築できます。
import express from 'express';
import multer from 'multer';
const app = express();
const imageFilter = (req: any, file: Express.Multer.File, cb: any) => {
if (file.mimetype === 'image/png' || file.mimetype === 'image/jpeg') {
cb(null, true);
} else {
cb(new Error('画像ファイルのみアップロード可能です'), false);
}
};
const upload = multer({
dest: 'uploads/',
fileFilter: imageFilter
});
app.post('/gallery', upload.single('galleryFile'), (req, res) => {
res.send('安全な画像ファイルを確認して保存しました');
});
このように、事前に設定したルールに合格したファイルだけが保存用のフォルダに進むことができるようになり、バックエンドシステムの安全性が大幅に向上します。
8. 複数のファイルを一度に受け取る複数アップロード
これまでは、一つのファイルだけを処理する方法を見てきましたが、実際のウェブサイトでは、複数の画像を一括して送信したい場面も多く存在します。Multerでは、一つの枠から複数のファイルを受け取る方法や、異なる名前の枠からそれぞれファイルを受け取る方法など、多様な形式に対応しています。
複数のファイルを扱う場合、プログラムの中ではデータが配列という箱にまとめて入れられます。TypeScriptでは、単数のファイルと複数のファイルの配列を明確に区別して書かなければいけないため、ここでも型のチェックが真価を発揮します。間違えて単数用の命令を使ってしまうと、パソコンがエラーを出して教えてくれます。以下は、最大五つまでのファイルを同時に受け取るプログラムです。
import express from 'express';
import multer from 'multer';
const app = express();
const upload = multer({ dest: 'uploads/' });
app.post('/multi-upload', upload.array('manyFiles', 5), (req, res) => {
const files = req.files as Express.Multer.File[];
if (!files || files.length === 0) {
res.status(400).send('ファイルが送信されていません');
return;
}
res.send(files.length + '個のファイルを正常に受け取りました');
});
このように、送信されてくるデータの量や形式に合わせてプログラムを柔軟に変更しつつ、TypeScriptの型をしっかりと指定しておくことで、大人数が同時に利用するシステムでも壊れない頑丈なバックエンドを構築することができます。