{"id":23143732,"url":"https://github.com/xieyuheng/x-server","last_synced_at":"2025-08-17T14:33:48.951Z","repository":{"id":194905546,"uuid":"691204870","full_name":"xieyuheng/x-server","owner":"xieyuheng","description":"A website server that supports serving many websites using subdomain-based routing.","archived":false,"fork":false,"pushed_at":"2025-07-20T06:30:07.000Z","size":562,"stargazers_count":23,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-08-16T05:27:44.489Z","etag":null,"topics":["subdomain-routing","website-server"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/xieyuheng.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE-OF-CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-09-13T17:48:42.000Z","updated_at":"2025-07-20T06:30:10.000Z","dependencies_parsed_at":"2023-09-15T18:20:16.068Z","dependency_job_id":null,"html_url":"https://github.com/xieyuheng/x-server","commit_stats":null,"previous_names":["xieyuheng/x-server"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/xieyuheng/x-server","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xieyuheng%2Fx-server","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xieyuheng%2Fx-server/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xieyuheng%2Fx-server/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xieyuheng%2Fx-server/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xieyuheng","download_url":"https://codeload.github.com/xieyuheng/x-server/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xieyuheng%2Fx-server/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270859544,"owners_count":24658221,"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","status":"online","status_checked_at":"2025-08-17T02:00:09.016Z","response_time":129,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["subdomain-routing","website-server"],"created_at":"2024-12-17T15:14:06.909Z","updated_at":"2025-08-17T14:33:48.909Z","avatar_url":"https://github.com/xieyuheng.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# X Server\n\nA website server\nthat supports serving many websites\nusing subdomain-based routing.\n\n## Install\n\nInstall it by the following command:\n\n```sh\nnpm install -g @xieyuheng/x-server\n```\n\nThe command line program is called `x-server`.\n\n```\nCommands:\n  help [name]             Display help for a command\n  serve:website [path]    Serve a website\n  serve:subdomain [path]  Serve many websites using subdomain-based routing\n```\n\n## Docs\n\n- [Serve one website](#serve-one-website)\n- [Serve many websites](#serve-many-websites)\n- [Config logger](#config-logger)\n- [Use custom domain](#use-custom-domain)\n- [Get free certificate](#get-free-certificate)\n- [Use systemd to start service](#use-systemd-to-start-service)\n\n### Serve one website\n\nUse the `x-server serve:website` command to serve one website:\n\n```sh\nx-server serve:website \u003cdirectory\u003e\n```\n\nWhen serving a [single-page application (SPA)](https://en.wikipedia.org/wiki/Single-page_application),\nwe need to redirect all requests to `index.html`.\n\nExample command for serving a SPA:\n\n```sh\nx-server serve:website \u003cdirectory\u003e \\\n  --cors \\\n  --redirect-not-found-to index.html \\\n  --cache-control-pattern 'assets/**: max-age=31536000'\n```\n\nTo serve a website using HTTPS, we need to provide TLS certificate.\n\nExample command for serving a SPA using HTTPS:\n\n```sh\nx-server serve:website \u003cdirectory\u003e \\\n  --cors \\\n  --port 443 \\\n  --redirect-not-found-to index.html \\\n  --cache-control-pattern 'assets/**: max-age=31536000' \\\n  --tls-cert \u003ccertificate-file\u003e \\\n  --tls-key \u003cprivate-key-file\u003e\n```\n\nIt is unhandy to issue long command,\nthus we also support using a `website.json` config file:\n\n```sh\nx-server serve:website \u003cdirectory\u003e/website.json\n```\n\nWhere `\u003cdirectory\u003e/website.json` is:\n\n```json\n{\n  \"server\": {\n    \"port\": 443,\n    \"tls\": {\n      \"cert\": \"\u003ccertificate-file\u003e\",\n      \"key\": \"\u003cprivate-key-file\u003e\"\n    }\n  },\n  \"cors\": true,\n  \"redirectNotFoundTo\": \"index.html\",\n  \"cacheControlPatterns\": {\n    \"assets/**\": \"max-age=31536000\"\n  }\n}\n```\n\n### Serve many websites\n\nThe main use case of `x-server` is to\nserve many websites in one directory,\nusing subdomain-based routing.\n\nFor example, I have a VPS machine,\nwhere I put all my websites\nin the `/websites` directory.\n\n```\n/websites/www\n/websites/graphwind\n/websites/inet\n/websites/pomodoro\n/websites/readonlylink\n...\n```\n\nI bought a domain for my server -- say `example.com`,\nand configured my DNS to resolve `example.com`\nand `*.example.com` to my server.\n\nI also created certificate files for my domain using `certbot`.\n\n- About how to use `certbot`, please see\n  the [\"Get free certificate\"](#get-free-certificate) section.\n\nI can use `x-server serve:subdomain` command to serve all of\nthe websites in `/websites` directory.\n\n```sh\nx-server serve:subdomain /websites \\\n  --hostname example.com \\\n  --port 443 \\\n  --tls-cert /etc/letsencrypt/live/example.com/fullchain.pem \\\n  --tls-key /etc/letsencrypt/live/example.com/privkey.pem\n```\n\nThen I can visit all my websites via subdomain of `example.com`.\n\n```\nhttps://www.example.com\nhttps://graphwind.example.com\nhttps://inet.example.com\nhttps://pomodoro.example.com\nhttps://readonlylink.example.com\n...\n```\n\nIf no subdomain is given in a request,\n`www/` will be used as the default subdomain directory\n(while no redirect will be done).\n\nThus the following websites have the same contents:\n\n```\nhttps://example.com\nhttps://www.example.com\n```\n\nInstead of issuing long command,\nwe can also use a root `website.json` config file.\n\n```sh\nx-server serve:subdomain /websites/website.json\n```\n\nWhere `/websites/website.json` is:\n\n```json\n{\n  \"server\": {\n    \"hostname\": \"example.com\",\n    \"port\": 443,\n    \"tls\": {\n      \"cert\": \"/etc/letsencrypt/live/example.com/fullchain.pem\",\n      \"key\": \"/etc/letsencrypt/live/example.com/privkey.pem\"\n    }\n  }\n}\n```\n\n- When using `x-server serve:subdomain`,\n  the `server.hostname` option is required.\n\n- And each website in `/websites` might have\n  it's own `website.json` config file.\n\n### Config logger\n\nWe can config logger in `/websites/website.json`:\n\n```json\n{\n  ...,\n  \"logger\": {\n    \"name\": \"pretty-line\",\n    \"disableRequestLogging\": true\n  }\n}\n```\n\nThe type of logger options are:\n\n```ts\nexport type LoggerOptions = {\n  name: \"json\" | \"silent\" | \"pretty\" | \"pretty-line\"\n  disableRequestLogging?: boolean\n}\n```\n\nThe default logger options are:\n\n```json\n{\n  \"name\": \"pretty-line\",\n  \"disableRequestLogging\": false\n}\n```\n\n### Use custom domain\n\nWhen doing subdomain-based routing,\nwe can also support custom domain for a subdomain,\nby adding a file in `.domain-map/` directory.\n\n```\n/websites/.domain-map/\u003ccustom-domain\u003e/subdomain\n```\n\nWhere the content of the file is the subdomain, for examples:\n\n```\n/websites/.domain-map/readonly.link/subdomain -- (content: readonlylink)\n...\n```\n\nThen I can the following DNS ALIAS records to my custom domains:\n\n- You can also use A record and IP addresses.\n\n| Domain        | Type  | Value                  |\n|---------------|-------|------------------------|\n| readonly.link | ALIAS | readonlylink.example.com. |\n\n\nCustom domain is only supported when TLS is enabled.\nTo provide TLS certificate for a custom domain,\nadd the following files:\n\n```\n/websites/.domain-map/\u003ccustom-domain\u003e/cert\n/websites/.domain-map/\u003ccustom-domain\u003e/key\n```\n\nFor example, the listing of `.domain-map/` is the following:\n\n```\n/websites/.domain-map/readonly.link/subdomain\n/websites/.domain-map/readonly.link/cert\n/websites/.domain-map/readonly.link/key\n...\n```\n\n### Get free certificate\n\nYou can use `certbot` to get free certificate for your domains.\n\n- [Certbot website](https://certbot.eff.org/instructions)\n- [Certbot on archlinux wiki](https://wiki.archlinux.org/title/certbot)\n\nAfter install `certbot`,\nI prefer creating certificate via DNS TXT record,\nusing the following command:\n\n```sh\nsudo certbot certonly --manual --preferred-challenges dns\n```\n\nThen you can follow the prompt of `certbot`\nto create the certificate files,\nduring which you will need to add TXT record\nto the DNS record of your domain\nto accomplish the challenge given by `certbot`.\n\nAfter created the certificate files,\nI use the follow command to copy them to my `.domain-map`:\n\n```sh\nsudo cat /etc/letsencrypt/live/\u003ccustom-domain\u003e/fullchain.pem \u003e /websites/.domain-map/\u003ccustom-domain\u003e/cert\nsudo cat /etc/letsencrypt/live/\u003ccustom-domain\u003e/privkey.pem \u003e /websites/.domain-map/\u003ccustom-domain\u003e/key\n```\n\n### Use systemd to start service\n\nOn a Linux server, we can use `systemd` to start a service,\nor enable a service to start whenever the server is booted.\n\n\nExample service file `fidb-app-x-server.service`:\n\n```\n[Unit]\nDescription=example.com x-server\nAfter=network.target\n\n[Service]\nExecStart=/usr/local/bin/x-server serve:subdomain /websites/website.json\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\n```\n\nInstall service:\n\n```\nsudo cp \u003cname\u003e.service /etc/systemd/system/\n```\n\nUsing service:\n\n```\nsudo systemctl start \u003cname\u003e.service\nsudo systemctl enable \u003cname\u003e.service\nsudo systemctl status \u003cname\u003e.service\n```\n\nTo view log:\n\n```\njournalctl -f -u \u003cname\u003e.service\n```\n\nReload systemd config files:\n\n```\nsudo systemctl daemon-reload\n```\n\n## Development\n\n```sh\nnpm install           # Install dependencies\nnpm run build         # Compile `src/` to `lib/`\nnpm run build:watch   # Watch the compilation\nnpm run format        # Format the code\nnpm run test          # Run test\nnpm run test:watch    # Watch the testing\n```\n\n## License\n\n[GPLv3](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxieyuheng%2Fx-server","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxieyuheng%2Fx-server","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxieyuheng%2Fx-server/lists"}