BankingMockAPI#
A simple mock banking API for testing and prototyping.
Provides authentication, accounts, cards, and transactions endpoints backed by a seeded SQLite database.
Includes a React Native demo app powered by Expo.
🚀 Getting Started#
Prerequisites#
-
Node.js v18 or higher
-
Docker (optional, for containerized setup)
-
Expo CLI (for running the React Native app):
npm install -g expo
Installation#
git clone git@tangled.sh:mokkenstorm.dev/react-native-demo
cd react-native-demo
Running the Server#
Run directly with Node.js#
cd server && npx ts-node src/server.ts
The server will be available at:
👉 http://localhost:3001
Run with Docker Compose#
docker compose up --build
Stop the service:
docker compose down
Server base URL: http://localhost:3001
🎨 Running the App#
The repository includes a React Native demo app that connects to the API.
To generate client code from the OpenAPI spec and start the app with Expo:
cd app
npm run generate
npm run ios
You can replace npm run ios with:
npm run android— to run on Androidnpm run web— to run in a web browser
⚠️ Note: The app requires and API server (see instructions above).
Without the server running on http://localhost:3001, login and data
fetches will fail by default. If you need a different backend port/url
and set the SERVER_URL environment variable before generating the
client code, and optionally configure the compose.yaml for a different port.
Expo will guide you through launching the app on your chosen platform.
📚 API Documentation#
Interactive documentation and the raw OpenAPI specification are exposed by the server:
-
Swagger UI: http://localhost:3001
A browsable interface to test and explore the API endpoints. -
OpenAPI Spec (YAML): http://localhost:3001/openapi.yaml
The raw machine-readable specification, suitable for client generation.
📖 API Endpoints#
All protected routes require a JWT in the Authorization header:
Authorization: Bearer <JWT>
🔑 Authentication#
POST /login#
Authenticate and receive a token pair.
Request Body
{
"username": "test@test.test",
"password": "password@123"
}
Response 200**
{
"accessToken": "<JWT>",
"refreshToken": "<refresh_JWT>",
"expires": "2024-06-01T10:05:00Z"
}
Response 401**
{
"errors": [],
"properties": {
"username": { "errors": ["Invalid email address"] },
"password": {
"errors": ["Too small: expected string to have >=8 characters"]
}
}
}
POST /refresh-token#
Exchange a refresh token for a new pair.
Request Body
{
"refreshToken": "<refresh_JWT>"
}
Response 200**
{
"accessToken": "<new_JWT>",
"refreshToken": "<new_refresh_JWT>"
}
👤 Me#
GET /me#
Returns the authenticated user.
Response 200**
{
"id": 2,
"username": "nmokkenstorm",
"fullname": "Niels Mokkenstorm",
"created": "2024-06-01T10:00:00Z"
}
🏦 Accounts#
GET /accounts#
List accounts for the authenticated user.
Response 200**
[
{
"id": 1,
"user_id": 1,
"iban": "NL00BANK0123456789",
"name": "Checking",
"balance": 1680.16
}
]
GET /accounts/{accountId}#
Retrieve a single account by ID.
Response 200**
{
"id": 1,
"user_id": 1,
"iban": "NL00BANK0123456789",
"name": "Checking",
"balance": 1680.16
}
💳 Cards#
GET /cards#
List stored cards (demo only: returns full PAN and CVV).
Response 200**
[
{
"id": 1,
"user_id": 1,
"number": "4111111111111111",
"expiry": "12/26",
"cvv": "123"
}
]
🧾 Transactions#
GET /transactions#
Search/sort/paginate transactions. Query params: search, sort (default date), order (asc|desc, default desc), page (default 1), limit (default 25), accountId, type.
Response 200**
{
"data": [
{
"id": 1,
"userId": 1,
"accountId": 1,
"amount": -50.25,
"type": "debit",
"description": "Grocery Store",
"date": "2024-06-01T10:00:00Z"
}
],
"meta": {
"page": 1,
"limit": 25,
"hasMore": false,
"total": 1
}
}
GET /transaction-types#
Aggregate transaction types.
Response 200**
[
{ "name": "debit", "count": 12 },
{ "name": "credit", "count": 5 }
]
👤 Default Test User#
- Username:
test@test.test - Password:
password@123
🗄️ Database#
- Uses SQLite.
- The database is reset and seeded with test data on every server start.