{"id":21103553,"url":"https://github.com/oliverjam/node-file-server","last_synced_at":"2026-01-17T07:14:30.778Z","repository":{"id":53085264,"uuid":"248586889","full_name":"oliverjam/node-file-server","owner":"oliverjam","description":"Learn to serve static files in Node","archived":false,"fork":false,"pushed_at":"2021-04-07T11:05:42.000Z","size":241,"stargazers_count":0,"open_issues_count":0,"forks_count":4,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-02-09T04:16:20.176Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/oliverjam.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-03-19T19:22:41.000Z","updated_at":"2021-04-07T11:05:18.000Z","dependencies_parsed_at":"2022-09-12T12:30:22.555Z","dependency_job_id":null,"html_url":"https://github.com/oliverjam/node-file-server","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oliverjam%2Fnode-file-server","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oliverjam%2Fnode-file-server/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oliverjam%2Fnode-file-server/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oliverjam%2Fnode-file-server/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/oliverjam","download_url":"https://codeload.github.com/oliverjam/node-file-server/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247025704,"owners_count":20871237,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-19T23:59:10.695Z","updated_at":"2026-01-17T07:14:30.751Z","avatar_url":"https://github.com/oliverjam.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Node file server\n\nAn introduction to accessing and serving files with Node.\n\n## Web servers\n\nHere's a quick example of how we might send an HTML response using Node:\n\n```js\nfunction router(request, response) =\u003e {\n  response.writeHead(200, { \"content-type\", \"text/html\" });\n  response.end(\"\u003ch1\u003ehello\u003c/h1\u003e\");\n});\n```\n\nIt's quick and easy to send a response body using JS strings. However for more complex response types this can get difficult.\n\nFor example we can serve a simple SVG image inline:\n\n```js\nresponse.end(`\n\u003csvg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\"\u003e\n  \u003crect width=\"50\" height=\"50\" x=\"25\" y=\"25\" fill=\"red\" /\u003e\n\u003c/svg\u003e\n`);\n```\n\nbut serving binary file types (like a JPEG image) this way would be difficult. If you open a JPEG in a text editor you'll see a huge blob of incomprehensibly encoded characters. It's easier to store these as separate files and read their content when we need it.\n\n## Setup\n\n1. Clone this repo\n1. Run `npm install` to install the project's dependencies\n\nThis project has the `nodemon` module installed as a devDependency (you can see this in the `package.json`). This is a useful tool that will watch your files for changes and restart your server automatically when you save.\n\n## Using `fs`\n\nOpen `workshop/practice.js` in your editor. Run `npm run practice` in your terminal to make your code auto-restart when you save changes.\n\nNode provides the `fs` module for working with your computer's file-system. We can import it using Node's `require` syntax:\n\n```js\nconst fs = require(\"fs\");\n```\n\n`fs` is an object with lots of useful methods. Right now we want to read the contents of a file, so we'll use the `fs.readFile` method. It takes a file path as the first argument and a callback function for it to run once the file is available.\n\n```js\nconst fs = require(\"fs\");\n\nfs.readFile(\"workshop/test.txt\", (error, file) =\u003e {\n  console.log(file);\n});\n```\n\nRun this and you should see something like `\u003cBuffer 53 6f...\u003e` logged. This is a [chunk of memory](https://nodejs.dev/nodejs-buffers) representing the contents of the file. If you want to see it represented as a JS string you can pass the file's encoding as the second argument:\n\n```js\nconst fs = require(\"fs\");\n\nfs.readFile(\"workshop/test.txt\", \"utf-8\", (error, file) =\u003e {\n  console.log(file);\n});\n```\n\nThis isn't necessary to _use_ the file contents (since Node understands buffers), just to print them in a format we can read.\n\n### Error-handling\n\nThere are quite a few things that could go wrong when dealing with the file-system, so it's important to make sure we handle errors. Change the file path you're reading to `\"not-real.txt\"`. Now when you run the script you should see `undefined` logged.\n\n#### Error first callbacks\n\nSince callbacks have no built-in way to handle errors (unlike promises with their `.catch` method) Node relies on a convention. All callbacks will be called with a possible error as the first argument and the actual thing you want as the second. If there was no error this argument will be `null`.\n\nSo generally you want to handle the error first in your callback, then deal with the \"happy case\".\n\n```js\nfs.readFile(\"not-real.txt\", \"utf-8\", (error, file) =\u003e {\n  if (error) {\n    console.log(error);\n  } else {\n    console.log(file);\n  }\n});\n```\n\nRun this and you should see an error containing `\"no such file or directory\"`. For an HTTP server you would probably want to send a `404` status code and an error message back to the page.\n\n### Cross-platform paths\n\nFile paths are actually quite complicated. The one we hard-coded above works on most Unix systems (Mac and Linux) but would probably break on Windows, since that uses backslashes to separate files. Node has built-in helpers to create cross-platform paths.\n\nWe can use the `path` module's `path.join()` method to join strings together correctly.\n\n```js\nconst fs = require(\"fs\");\nconst path = require(\"path\");\n\nfs.readFile(path.join(\"workshop\", \"test.txt\"), (error, file) =\u003e {});\n```\n\nThere's one more problem: the path we've written is relative to the directory we ran our JS file from. `cd` into the `workshop` folder, then run `node practice.js`. You should see an error logged, because there is no `\"workshop\"` directory inside of `workshop/`.\n\nNode provides a global variable called `__dirname` (that's two underscores). This will always be the path to the directory the currently executing file is inside. So in this case it will always be `stuff/on/your/computer/node-file-server/workshop/`. This make it safe to use in our `readFile`: it will always be correct no matter where we start our program.\n\n```js\nfs.readFile(path.join(__dirname, \"test.txt\"), (error, file) =\u003e {});\n```\n\n## MIME types\n\nMultipurpose Internet Mail Extensions (or MIME type) is a standard that determines what format a file is. You've encountered them already in the `content-type` HTTP header (like `\"application/json\"` or `\"text/html\"`).\n\nMIME types are structured as `generic/specific`. For example `text/plain`, `text/html` and `text/css` are all kinds of text file, whereas `image/png` and `image/jpeg` are kinds of image file.\n\n### `content-type`\n\nIt's very important to set the `content-type` header correctly, as web browsers **ignore file extensions** and rely on this header to parse a file. This means if you serve `my-site.com/styles.css` with a `content-type` of `text/html` the browser may not use it properly.\n\nIt's easy to do this manually for one-off files, but if you want to write a generic endpoint that can serve any static file you need to map file extensions to MIME types:\n\n```js\nconst path = require(\"path\");\n\nconst types = {\n  \".html\": \"text/html\",\n  \".css\": \"text/css\",\n  \".js\": \"application/javascript\",\n};\n\nfunction router(request, response) {\n  const extension = path.extname(request.url); // gets the file extension e.g. \"styles/my-file.css\" -\u003e \".css\"\n  const type = types[extension]; // e.g. \"text/css\"\n  response.writeHead(200, { \"content-type\": type });\n  // ...\n}\n```\n\n## Workshop\n\n1. Open `workshop/server.js` in your editor\n1. Run `npm run dev`, then open `http://localhost:3000` in your browser\n1. You should see an HTML page loaded, but with no styles\n   - Check the network tab and you'll see failing requests for `.css`, `.js`, `.ico` and `.jpg` files\n1. Edit the `handlers/public.js` to make these requests work\n   - Make sure they have the correct `content-type` header\n\n**Hint**: take a look at `handlers/home.js` for a refresher on `readFile` and paths.\n\n![](https://user-images.githubusercontent.com/9408641/77124124-eff96300-6a39-11ea-8230-ff5cd2f3e398.png)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foliverjam%2Fnode-file-server","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foliverjam%2Fnode-file-server","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foliverjam%2Fnode-file-server/lists"}