Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions .github/workflows/api.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
name: API
name: api

on:
push:
branches: [main]
pull_request:
paths:
- 'apps/api/**'
- 'packages/shared/**'
- 'package.json'
- 'package-lock.json'
- '.github/workflows/api.yml'

jobs:
api:
Expand Down
7 changes: 1 addition & 6 deletions .github/workflows/mobile.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
name: Mobile
name: mobile

on:
push:
branches: [main]
pull_request:
paths:
- 'apps/mobile/**'
- 'package.json'
- 'package-lock.json'
- '.github/workflows/mobile.yml'

jobs:
mobile:
Expand Down
18 changes: 3 additions & 15 deletions .github/workflows/shared.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
name: Shared & Stellar Packages
name: shared

on:
push:
branches: [main]
pull_request:
paths:
- 'packages/shared/**'
- 'packages/stellar/**'
- 'package.json'
- 'package-lock.json'
- '.github/workflows/shared.yml'

jobs:
shared:
Expand All @@ -25,14 +19,8 @@ jobs:
- name: Install dependencies
run: npm ci

- name: Lint shared
- name: Lint
run: npm run lint -w @discoverly/shared

- name: Build shared
- name: Build
run: npm run build -w @discoverly/shared

- name: Lint stellar
run: npm run lint -w @discoverly/stellar

- name: Build stellar
run: npm run build -w @discoverly/stellar
8 changes: 1 addition & 7 deletions .github/workflows/web.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
name: Web
name: web

on:
push:
branches: [main]
pull_request:
paths:
- 'apps/web/**'
- 'packages/shared/**'
- 'package.json'
- 'package-lock.json'
- '.github/workflows/web.yml'

jobs:
web:
Expand Down
77 changes: 23 additions & 54 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,64 +1,33 @@
# compiled output
/dist
/node_modules
**/node_modules
/build
**/dist
**/dist-export
**/.expo
**/.turbo
**/.next
# Local planning docs
.sprint-plan.md

# Dependencies
node_modules/

# Build output
dist/
build/
.next/
*.tsbuildinfo

# Expo
.expo/
.expo-shared/

# Env
.env
.env.local
.env.*.local

# Logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# OS
# Editor
.vscode/
.idea/
.DS_Store

# Tests
/coverage
/.nyc_output

# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace

# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# temp directory
.temp
.tmp

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Test coverage
coverage/
1 change: 0 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,4 @@ build
.next
.expo
coverage
*.lock
package-lock.json
5 changes: 3 additions & 2 deletions apps/api/.env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
NODE_ENV=development
PORT=5000
NODE_ENV=development
MONGODB_URI=mongodb://localhost:27017/discoverly
JWT_SECRET=replace_me
JWT_SECRET=replace-with-a-long-random-secret
JWT_EXPIRES_IN=7d
CORS_ORIGIN=http://localhost:3000
8 changes: 2 additions & 6 deletions apps/api/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,10 @@ module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: './tsconfig.json',
sourceType: 'module',
tsconfigRootDir: __dirname,
},
plugins: ['@typescript-eslint'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
],
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
env: {
node: true,
es2022: true,
Expand Down
48 changes: 22 additions & 26 deletions apps/api/README.md
Original file line number Diff line number Diff line change
@@ -1,49 +1,45 @@
# @discoverly/api

Express + TypeScript backend, structured as a modular monolith.
Express + TypeScript modular monolith. Currently implements the **auth**
module (registration, login, and an authenticated `/me` endpoint) and a
supporting **users** module.

## Structure

```
src/
app.ts # Express app wiring (middleware, routes)
server.ts # Entry point: connects to MongoDB and starts the server
modules/
auth/ # registration, login, JWT issuance, auth middleware
users/ # user persistence (Mongoose model + repository)
auth/ # Controllers, services, routes, validators, middleware, tests
users/ # User model, repository, service
shared/
config/ # environment validation
database/ # MongoDB connection helper
errors/ # AppError
logger/ # JSON logger
middleware/# error handler, 404 handler
types/ # shared Express types
app.ts # express app wiring
server.ts # process entrypoint
config/ # Environment validation
database/ # MongoDB connection helper
errors/ # AppError and HTTP error classes
logger/ # Lightweight structured logger
middleware/ # Error handler, 404 handler
types/ # Express type augmentations
```

Each module owns its controllers, services, repositories, validators, routes,
types, and tests. Cross-cutting infrastructure lives under `src/shared`.

## Development
## Running

```bash
cp .env.example .env
npm run dev -w @discoverly/api
```

## Testing
The API listens on `http://localhost:5000` and exposes:

Tests use Vitest, Supertest, and an in-memory MongoDB instance
(`mongodb-memory-server`), so no external database is required:
- `POST /api/auth/register`
- `POST /api/auth/login`
- `GET /api/auth/me` (requires `Authorization: Bearer <token>`)
- `GET /api/health`

## Testing

```bash
npm run test -w @discoverly/api
```

## API

| Method | Path | Auth | Description |
| ------ | ---------------- | ---- | ------------------------ |
| POST | `/api/auth/register` | No | Create an account |
| POST | `/api/auth/login` | No | Log in and receive a JWT |
| GET | `/api/auth/me` | Yes | Get the current user |
| GET | `/api/health` | No | Health check |
Tests use `mongodb-memory-server`, so no external database is required.
27 changes: 12 additions & 15 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,41 @@
"name": "@discoverly/api",
"version": "0.1.0",
"private": true,
"type": "module",
"description": "Discoverly API - modular monolith (authentication module)",
"main": "dist/server.js",
"scripts": {
"dev": "tsx watch src/server.ts",
"build": "tsc -p tsconfig.json",
"start": "node dist/server.js",
"lint": "eslint \"src/**/*.ts\"",
"typecheck": "tsc --noEmit",
"test": "vitest run"
"test": "vitest run",
"test:watch": "vitest",
"clean": "rm -rf dist"
},
"dependencies": {
"@discoverly/shared": "*",
"@discoverly/shared": "^0.1.0",
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"express-rate-limit": "^7.4.1",
"helmet": "^7.1.0",
"jsonwebtoken": "^9.0.2",
"mongoose": "^8.6.2",
"morgan": "^1.10.0",
"mongoose": "^8.5.2",
"zod": "^3.23.8"
},
"devDependencies": {
"@types/bcryptjs": "^2.4.6",
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
"@types/jsonwebtoken": "^9.0.6",
"@types/morgan": "^1.9.9",
"@types/node": "^20.16.5",
"@types/node": "^20.14.15",
"@types/supertest": "^6.0.2",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"eslint": "8.57.1",
"eslint-config-prettier": "^9.1.0",
"eslint": "^8.57.0",
"mongodb-memory-server": "^9.4.1",
"supertest": "^7.0.0",
"tsx": "^4.19.1",
"typescript": "^5.6.2",
"vitest": "^2.1.1"
"tsx": "^4.17.0",
"typescript": "^5.5.4",
"vitest": "^2.0.5"
}
}
19 changes: 5 additions & 14 deletions apps/api/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,15 @@
import cors from 'cors';
import express, { type Express } from 'express';
import { rateLimit } from 'express-rate-limit';
import helmet from 'helmet';
import morgan from 'morgan';
import { authRouter } from './modules/auth/routes/auth.routes.js';
import { errorHandler } from './shared/middleware/error-handler.js';
import { notFoundHandler } from './shared/middleware/not-found.js';
import { env } from './shared/config/env';
import { errorHandler } from './shared/middleware/error-handler';
import { notFoundHandler } from './shared/middleware/not-found';
import { authRouter } from './modules/auth/routes/auth.routes';

export function createApp(): Express {
const app = express();

app.use(helmet());
app.use(cors());
app.use(cors({ origin: env.CORS_ORIGIN }));
app.use(express.json());
app.use(morgan('dev'));

app.use(
'/api/auth',
rateLimit({ windowMs: 15 * 60 * 1000, limit: 100, standardHeaders: true, legacyHeaders: false }),
);

app.get('/api/health', (_req, res) => {
res.status(200).json({ status: 'ok' });
Expand Down
Loading
Loading