LinkLibrarian is a full-stack web app for registering users, logging in, and saving, editing, filtering, and deleting personal links. It includes an image upload functionality to customize a user's saved links and incorporates two workflows for automatic linting and several basic software tests.
- Frontend: React + Vite
- Backend: Node.js + Express (with multer for image uploads)
- Database: MySQL
- Sessions: express-session + MySQL session store
- Hosting: Railway
- Testing: Jest + Supertest (backend tests)
project-root/
backend/
coverage/
tests/
uploads/
frontend/
public/
src/
database/
.github/workflows/
backend/contains the Express server, session handling, and MySQL connection.backend/coverage/contains the coverage output reports from the software tests.backend/tests/contains the software test files.backend/uploads/is created automatically when you upload an image. It stores user-uploaded link images, which are served at/uploads/<filename>.
frontend/contains the Vite + React client.frontend/public/holds the .svg template images for Vite & React.frontend/src/contains the .css & .jsx files for app stylization and the main function description.
database/contains the database configuration files..github/workflows/contains the .yml configuration files for the workflows used in testing and automatic linting of the app.
- User registration and login
- Session-based authentication
- Create, read, update, and delete links
- Tag filtering
- Upload and display an image for each link
- Local development support
- Production deployment on Railway
Open a terminal in the backend/ folder and run:
npm install
npm startThe backend runs on:
http://localhost:3001
You can verify it with:
http://localhost:3001/http://localhost:3001/api/test-db
Open a second terminal in the frontend/ folder and run:
npm install
npm run devThe frontend runs on:
http://localhost:5173
This project uses Vite environment variables for switching between local and production API URLs. Vite exposes only variables prefixed with VITE_ to client-side code.
VITE_API_BASE_URL=http://localhost:3001This is used for local development.
VITE_API_BASE_URL=https://linklibrarian-production.up.railway.appThis is used for production builds.
In frontend/src/App.jsx, the API base URL is read like this:
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3001';This app is deployed on Railway as three services:
- MySQL database
- Express backend
- Vite/React frontend
https://linklibrarian-frontend-production.up.railway.app
https://linklibrarian-production.up.railway.app
The frontend service is configured with:
- Root Directory:
/frontend - Public domain target port:
8080
Railway detects the frontend as a Vite static site, builds it into dist, and serves it with Caddy.
The backend allows both local development and the Railway-hosted frontend by using an allowlist in server.js.
Example:
const allowedOrigins = [
'http://localhost:5173',
'https://linklibrarian-frontend-production.up.railway.app'
];
app.use(cors({
origin: function (origin, callback) {
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true
}));Important:
- Do not add a trailing slash to allowed origins.
http://localhost:5173works, buthttp://localhost:5173/causes CORS errors.
In the Railway frontend service, set:
VITE_API_BASE_URL=https://linklibrarian-production.up.railway.app
Because Vite frontend variables are baked into the build, changing this value requires a rebuild/redeploy.
.env.localshould stay local and should not usually be committed.VITE_variables are not secret; they are visible in the frontend bundle.- Database passwords, session secrets, and backend credentials must stay in backend or Railway service variables, not frontend env files.
- Start local backend
- Start local frontend
- Open
http://localhost:5173 - Confirm changes affect local MySQL only
- Upload an image for a link and verify it appears in the UI.
- Open
https://linklibrarian-frontend-production.up.railway.app - Register/login successfully
- Confirm CRUD actions affect Railway MySQL
- Upload an image for a link and verify it appears in the UI.
Common causes:
- backend is not running locally
- frontend env variable points to the wrong API URL
- Railway frontend variable was changed but the app was not rebuilt
- CORS origin does not exactly match the frontend URL
Check:
- exact frontend origin
- no trailing slash in CORS allowlist
credentials: trueis enabled- deployed frontend URL is included in
allowedOrigins
Make sure both are running:
- backend at
http://localhost:3001 - frontend at
http://localhost:5173