A minimal MEAN stack (MongoDB, Express, Angular, Node.js) project for interns — a login form backed by a real Express API and a MongoDB database, with a default admin user created automatically.
Angular dev server (:4200) --/api/*--> Express (:3000) --Mongoose--> MongoDB (:27017)
- Angular (
src/) — the frontend. In development, its dev server proxies any request to/api/*straight through to the Express server, so the browser never needs to know about port 3000 directly and there's no CORS to think about. - Express (
server/) — the backend API. It exposesPOST /api/auth/login, which checks the submitted username/password against MongoDB. - MongoDB — the database. It stores user documents. Note: passwords are currently stored in plaintext on purpose — see Suggested Exercises for Interns. You can run MongoDB via Docker (no local install needed) or install it directly on your machine — see Prerequisites.
The frontend and backend are two independent Node projects (separate package.json, separate node_modules) living in one repository. This keeps the two stacks easy to reason about separately — you can work on the Angular app without touching the backend, and vice versa.
The Angular dev server runs on http://localhost:4200, and Express runs on a different port, http://localhost:3000. From a browser's point of view, those are two different origins (same host, different port still counts as a different origin), and browsers block a page from one origin from calling an API on another origin unless that API explicitly allows it — this is called CORS (Cross-Origin Resource Sharing). Without anything in place, AuthService's call to /api/auth/login would fail in the browser console with a CORS error, even though the request works fine from something like curl or Postman (those tools don't enforce CORS — only browsers do).
This project avoids the problem entirely in development: proxy.conf.json (wired up in angular.json) tells the Angular dev server "any request to /api/* isn't actually for me — silently forward it to http://localhost:3000 and hand back the response." The browser only ever talks to localhost:4200, so as far as it's concerned there's no cross-origin request happening at all, and no CORS error to hit.
The cors() middleware in server/src/app.ts is a second, independent layer of protection — it's what would matter if you called the API directly from a different origin (e.g. a production build served from a different domain, or a tool that bypasses the proxy). You don't need it for the standard npm run dev flow, but it's there so the API still works if the proxy isn't in the picture.
Make sure the following are installed before you begin:
| Tool | Required version | Check | Download |
|---|---|---|---|
| Node.js | 22.22.3 or newer | node --version |
nodejs.org/en/download |
| npm | 10 or newer | npm --version |
Included with Node.js |
| Angular CLI | 22.x | ng version |
npm install -g @angular/cli (see below) |
Install Angular CLI globally if you don't have it:
npm install -g @angular/cliYou need a MongoDB instance running on localhost:27017. Pick whichever is easier for you — the app doesn't care which one you use, since both are reachable at the same address.
| Option | Best for | Download |
|---|---|---|
| A. Docker (recommended) | No local install, easiest to reset/wipe | docker.com/get-started |
| B. MongoDB Community Server | If you'd rather not use Docker at all | mongodb.com/try/download/community |
- If you choose Docker: on Windows, make sure Docker Desktop is running and using the WSL2 backend (Docker Desktop → Settings → General → "Use the WSL 2 based engine"). Docker Desktop must be running before you start MongoDB with
npm run mongo:up(see Getting Started). - If you choose MongoDB Community Server: install it and make sure the MongoDB service is running locally (it listens on
localhost:27017by default, same as the Docker option). You can then skipnpm run mongo:up/npm run mongo:down/npm run mongo:logsentirely — those are Docker-only scripts.
Optional but recommended either way: MongoDB Compass is a free GUI for browsing your database. Once MongoDB is running, connect it to mongodb://localhost:27017 to inspect the angular_test database and its users collection visually instead of using the mongosh CLI.
git clone https://github.com/dogukan10/angular-test.git
cd angular-testnpm install
npm run server:installPick the option that matches what you installed in Prerequisites:
Option A — Docker:
npm run mongo:upThis starts a MongoDB container in the background (docker compose up -d). It stays running until you stop it with npm run mongo:down, so you typically only need to run this once per work session.
Option B — MongoDB Community Server: make sure the MongoDB service is running locally (it starts automatically as a background service on most installs — check your OS's services list if you're not sure). Nothing else to do here; skip npm run mongo:up.
npm run devThis runs the Angular dev server and the Express server side by side (via concurrently), both with live reload. Watch the terminal output for:
Server listening on http://localhost:3000andCreated default admin user "admin".(Express)Local: http://localhost:4200/(Angular)
Navigate to http://localhost:4200 in your browser. The login form appears immediately.
Credentials are validated against a real MongoDB document.
| Field | Value |
|---|---|
| Username | admin |
| Password | password123 |
Expected result: A green success banner appears with the message "Welcome, admin!"
Enter any other username/password combination (e.g. user / wrong) and click Login.
Expected result: A red error banner appears with the message "Invalid credentials. Please try again."
Leave either field blank and try to submit.
Expected result: The Login button is disabled and inline validation hints appear beneath the empty fields.
angular-test/
├── src/
│ ├── app/
│ │ ├── components/
│ │ │ └── login/
│ │ │ ├── login.ts # Login component — form logic, calls AuthService
│ │ │ ├── login.html # Form template with reactive bindings
│ │ │ └── login.css # Component-scoped styles
│ │ ├── services/
│ │ │ └── auth.service.ts # AuthService — calls the backend login API
│ │ ├── app.ts # Root component — hosts <app-login>
│ │ └── app.config.ts # Application-level providers (incl. HttpClient)
│ ├── styles.css # Global stylesheet
│ ├── index.html # HTML shell — Angular mounts inside <app-root>
│ └── main.ts # Entry point — bootstraps the application
├── server/ # Express + TypeScript backend (independent Node project)
│ ├── src/
│ │ ├── index.ts # Entry point — connects DB, creates the admin user, starts the server
│ │ ├── app.ts # Builds the Express app (middleware + routes)
│ │ ├── config/env.ts # Reads environment variables with sane defaults
│ │ ├── db/connection.ts # Connects to MongoDB via Mongoose
│ │ ├── models/user.model.ts # Mongoose User schema (username, password, role)
│ │ ├── controllers/auth.controller.ts # Login request handler
│ │ ├── routes/auth.routes.ts # POST /login route
│ │ └── admin/createAdminUser.ts # Creates the default admin user if it doesn't exist
│ └── package.json # Backend dependencies and scripts
├── docker-compose.yml # Spins up MongoDB for local development (only needed if using Docker)
├── proxy.conf.json # Angular dev-server proxy: /api/* → localhost:3000
├── package.json # npm dependencies and scripts (start, build, test, dev, ...)
└── angular.json # Angular CLI workspace configuration
Frontend
src/main.ts— The first file that runs. It callsbootstrapApplication(App, appConfig)to mount the Angular app into the browser.src/app/app.config.ts— Registers app-wide providers, includingprovideHttpClient()so services can make HTTP calls.src/app/app.ts— The root component (<app-root>). Currently it just renders<app-login>to keep the top level clean.src/app/components/login/login.ts— TheLogincomponent class. It creates a reactive form and, on submit,subscribe()s toAuthService.login()to update the result message.message/isSuccessare signals, so the template updates automatically whenever they change.src/app/components/login/login.html— The form template. Uses Angular's[formGroup]binding,@ifcontrol flow, and[class]binding to render validation messages and the result banner. Signals are read as functions (message(),isSuccess()).src/app/services/auth.service.ts— CallsPOST /api/auth/loginviaHttpClientand returns anObservable<{ success, message }>. Non-2xx responses (400/401/500) are caught withcatchErrorand turned into a normal{ success: false, message }emission, so components only need to handle the success path.
Backend
server/src/index.ts— Boots the server: connects to MongoDB, creates the default admin user, then starts listening.server/src/app.ts— Builds the Express app: CORS, JSON body parsing, and the/api/authroutes.server/src/config/env.ts— Centralizes all environment variables (port, Mongo URI, admin credentials, CORS origin) with defaults so the app runs even without a.envfile.server/src/db/connection.ts— Connects to MongoDB using Mongoose.server/src/models/user.model.ts— The Mongoose schema for users:username,password,role. TODO (intern exercise): the password is stored as plaintext — see Suggested Exercises for Interns.server/src/controllers/auth.controller.ts— HandlesPOST /api/auth/login: looks up the user, compares the password directly, and responds with a success or error message. TODO (intern exercise): swap the plaintext comparison for a hashed one.server/src/admin/createAdminUser.ts— Creates the defaultadminuser if it doesn't already exist. Runs automatically on server startup, and can also be run standalone vianpm run server:create-admin.
POST /api/auth/login (http://localhost:3000, proxied to /api/auth/login from the Angular app)
Request body:
{ "username": "admin", "password": "password123" }Response is always { "success": boolean, "message": string }:
| Case | Status | Message |
|---|---|---|
| Valid credentials | 200 | Welcome, <username>! |
| Missing username/password | 400 | Username and password are required. |
| Unknown user or wrong password | 401 | Invalid credentials. Please try again. |
| Server/database error | 500 | Something went wrong. Please try again later. |
- Recreate the admin user without losing other data:
npm run server:create-admin— safe to run any time; it does nothing if the admin user already exists. Works the same regardless of which MongoDB option you chose. - Wipe everything and start fresh:
- Docker:
npm run mongo:down docker compose down -v # -v also removes the Mongo data volume npm run mongo:up npm run dev # server automatically recreates the admin user on boot
- MongoDB Community Server: drop the database directly, then restart the backend:
mongosh --eval "use angular_test" --eval "db.dropDatabase()" npm run dev # server automatically recreates the admin user on boot
- Docker:
Run from the repository root:
| Command | Description |
|---|---|
npm run dev |
Start the Angular dev server and the Express server together, with live reload |
npm start |
Start only the Angular dev server at http://localhost:4200 |
npm run build |
Build the Angular app for production into dist/ |
npm test |
Run Angular unit tests with Vitest |
npm run mongo:up |
(Docker only) Start the MongoDB container (detached) |
npm run mongo:down |
(Docker only) Stop the MongoDB container |
npm run mongo:logs |
(Docker only) Tail the MongoDB container logs |
npm run server:install |
Install backend dependencies (server/node_modules) |
npm run server:dev |
Start only the Express server with live reload |
npm run server:create-admin |
Manually (re)create the default admin user |
Inside server/, the same backend scripts are also available directly: npm run dev, npm run build, npm start, npm run create-admin.
- Express fails to start with
ECONNREFUSED— MongoDB isn't running.- Docker: check
docker psforangular-test-mongo, make sure Docker Desktop is open, then runnpm run mongo:up. - MongoDB Community Server: check that the MongoDB service is actually running (check your OS's services list, or try starting it manually).
- Docker: check
- First
docker compose uptakes a while — Docker is downloading themongo:7image the first time. This is normal and only happens once. (Not applicable if you're using MongoDB Community Server.) - "Port already in use" — something else is using 3000, 4200, or 27017. Find and stop it:
If you change
netstat -ano | findstr :3000 taskkill /PID <pid> /F
PORTinserver/.env, also update thetargetinproxy.conf.jsonto match. - CORS error in the browser console — this shouldn't happen with the normal
npm run devflow, since the Angular dev server proxies/api/*requests to Express. It usually means the app was opened outside the dev server (e.g. a production build served directly) or an absolute URL was hardcoded somewhere instead of the relative/api/...path. - Login always fails right after cloning — check the Express startup logs for
Created default admin user "admin".. If it's missing, MongoDB probably wasn't reachable when the server started; confirm MongoDB is actually running (docker psfor Docker, or your OS's services list for MongoDB Community Server), then restart the server.
This project is intentionally left with a few rough edges to fix as learning exercises. They're marked with TODO (intern exercise) comments in the code:
- Hash passwords instead of storing them in plaintext.
server/src/models/user.model.tsstorespasswordas-is, andserver/src/controllers/auth.controller.tscompares it with===. Try using a library likebcryptorbcryptjsto hash the password before saving it (inserver/src/admin/createAdminUser.ts) and to compare it safely on login (inauth.controller.ts). After changing this, you'll need to reset the database (see Resetting the Database) since existing plaintext passwords won't match a hashed comparison.