Install and start the API
Install Node.js on the server, clone your fork, connect to the database and run the API as a systemd service.
About 35 min · Verified 8 October 2026
On this page
- Install the software
- Install Node.js 22 and Git
- Create the application folder
- Clone your fork
- Install the API's dependencies
- Configure the API
- Create the private env file
- Read the database password into a variable
- Write the settings file
- Create the tables
- Apply the database schema
- Run it as a service
- Create the systemd unit
- Start it
- Test it from the server itself
- Test it through the load balancer
- Call the API from your computer
- Create a link and follow it
The server is an empty Ubuntu machine. In this chapter you install Node.js, fetch your fork of the code, tell the API how to reach the database, create the tables, and run the API as a background service that restarts if it crashes.
Everything here runs on the EC2 instance through Session Manager. Open a session: EC2Instancesshortlink-apiConnectSession ManagerConnect.
Install the software#
Install Node.js 22 and Git#
The API needs Node.js 20 or newer. Ubuntu's own nodejs package can be older than that, so install from NodeSource, which serves current LTS versions.
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg git
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt-get install -y nodejsCheck the result. The service file later uses /usr/bin/node, so the path matters.
node --version
npm --version
command -v node
git --versionnode prints v22.x.x (anything v20 or newer is fine) and command -v node prints /usr/bin/node.Create the application folder#
sudo install -d -m 755 -o ubuntu -g ubuntu /opt/shortlinkThe service will run as the unprivileged ubuntu user, so that user owns the folder.
Clone your fork#
sudo -u ubuntu git clone --branch main https://github.com/<GITHUB_USER>/shortlink.git /opt/shortlink/repoReplace <GITHUB_USER> with your own value (or fill in the known ones once under My values at the top of the page).
Cloning into '/opt/shortlink/repo'... and no error.Install the API's dependencies#
sudo -u ubuntu npm --prefix /opt/shortlink/repo/shortlink-api ci --omit=devci installs exactly what package-lock.json says, and --omit=dev skips the test tools the server does not need.
Configure the API#
The API reads its settings from environment variables. They live in a file outside the repository so secrets never touch Git.
| Variable | Value | Why |
|---|---|---|
PORT | 3000 | Must match the target group and shortlink-api-sg |
DATABASE_URL | postgres://postgres:<password>@<RDS endpoint>:5432/shortlink | How to reach the database |
DB_SSL | true | RDS requires encrypted connections |
BASE_URL | <API_URL> | The host used in short links the API returns |
CORS_ORIGIN | <WEB_URL> | The website origin allowed to call the API from a browser |
Create the private env file#
sudo install -d -m 700 -o ubuntu -g ubuntu /etc/shortlink
sudo install -m 600 -o ubuntu -g ubuntu /dev/null /etc/shortlink/shortlink.envOnly the ubuntu user can read the folder or the file.
Read the database password into a variable#
Run this by itself. It prompts for the password and does not echo it to the screen or the shell history.
read -rsp "Paste the RDS master password, then press Enter: " DB_PASSWORD; echoWrite the settings file#
This URL-encodes the password (so characters like @ or / cannot break the URL) and writes the file in one go.
ENCODED=$(python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=""))' "$DB_PASSWORD")
sudo tee /etc/shortlink/shortlink.env >/dev/null <<EOF
PORT=3000
DATABASE_URL=postgres://postgres:${ENCODED}@<RDS_ENDPOINT>:5432/shortlink
DB_SSL=true
BASE_URL=<API_URL>
CORS_ORIGIN=<WEB_URL>
EOF
unset DB_PASSWORD ENCODED
sudo chown ubuntu:ubuntu /etc/shortlink/shortlink.env
sudo chmod 600 /etc/shortlink/shortlink.envReplace <RDS_ENDPOINT> <API_URL> <WEB_URL> with your own value (or fill in the known ones once under My values at the top of the page).
Check the file without printing the password:
sudo ls -l /etc/shortlink/shortlink.env
sudo sed 's/=.*/=…/' /etc/shortlink/shortlink.env-rw------- 1 ubuntu ubuntu 261 Oct 8 09:12 /etc/shortlink/shortlink.env
PORT=…
DATABASE_URL=…
DB_SSL=…
BASE_URL=…
CORS_ORIGIN=…If any value is still shown as a highlighted placeholder when you copy a command from this page, fill it in under My values first. A line such as BASE_URL=<API_URL> written literally into the file will break the API.
Create the tables#
Apply the database schema#
The repository includes an idempotent script (safe to run more than once) that creates the tables using the same driver the API uses, so you do not need the psql tool.
sudo -u ubuntu bash -c 'cd /opt/shortlink/repo/shortlink-api && node --env-file=/etc/shortlink/shortlink.env scripts/init-db.js'Schema appliedIt hangs, then prints ETIMEDOUT
The server cannot reach the database on port 5432. Check, in this order:
- The hostname in
DATABASE_URLequals the current endpoint ofshortlink-dbin the RDS console. Compare without printing the password:sudo sed -n 's/.*@\(.*\):5432.*/\1/p' /etc/shortlink/shortlink.env. shortlink-db-sgallows5432fromshortlink-api-sg(not from a CIDR).- The instance really uses
shortlink-api-sg: EC2InstanceSecurity. - The database Status is Available.
password authentication failed for user postgres
The password is wrong. Run the read step and the tee step again with the right one. A typo in the password manager copy is the usual cause.
database shortlink does not exist
You left Initial database name empty when creating the database. It is cleaner to delete the database and create it again with the name shortlink than to patch it by hand.
Run it as a service#
Create the systemd unit#
systemd starts the API at boot and restarts it if it crashes.
sudo tee /etc/systemd/system/shortlink-api.service >/dev/null <<'UNIT'
[Unit]
Description=ShortLink API
After=network.target
[Service]
Type=simple
User=ubuntu
WorkingDirectory=/opt/shortlink/repo/shortlink-api
ExecStart=/usr/bin/node --env-file=/etc/shortlink/shortlink.env src/server.js
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
UNITNotice the quotes around 'UNIT': they stop the shell from expanding anything inside the file.
Start it#
sudo systemctl daemon-reload
sudo systemctl enable --now shortlink-api
sudo systemctl is-active shortlink-apiactiveTest it from the server itself#
curl -fsS http://127.0.0.1:3000/health
echo
sudo journalctl -u shortlink-api -n 20 --no-pager{"status":"ok","database":"up"} and a journal line shortlink-api listening on 0.0.0.0:3000.If the service is failed, the journal shows why. The usual causes are a wrong env file (DATABASE_URL is not set) or an old Node version (node: bad option: --env-file).
Test it through the load balancer#
Call the API from your computer#
curl -i http://<ALB_DNS>/healthReplace <ALB_DNS> with your own value (or fill in the known ones once under My values at the top of the page).
HTTP/1.1 200 OK and {"status":"ok","database":"up"}.Give the target group about 30 seconds. Then in EC2Target Groupsshortlink-app-tgTargets the instance should show Healthy.
Create a link and follow it#
curl -sS -X POST http://<ALB_DNS>/api/links \
-H 'Content-Type: application/json' \
-d '{"url":"https://aws.amazon.com/","alias":"aws-test"}'Replace <ALB_DNS> with your own value (or fill in the known ones once under My values at the top of the page).
The response (HTTP 201) includes "shortUrl". Its host must be your load balancer, which proves BASE_URL is right. Now open the short link and check it redirects:
curl -sI http://<ALB_DNS>/aws-test | head -n 3Replace <ALB_DNS> with your own value (or fill in the known ones once under My values at the top of the page).
HTTP/1.1 302 Found
Location: https://aws.amazon.com/You now have a working backend: browser or curl → load balancer → private server → private database.
Next: deploy the website.
Found a mistake? Edit this page on GitHub.