Node.js + Express + PostgreSQL. Auth, wallets, transfers — raw SQL, no ORM.
- register and login with bcrypt + JWT
- create wallets per user (any ISO currency)
- deposit funds into a wallet
- transfer between wallets — wrapped in a postgres transaction with row-level locks so nothing goes wrong under concurrent load
- full transfer history with pagination and direction (incoming / outgoing)
- Node.js 18+
- Express 4
- PostgreSQL 14+
pg,bcryptjs,jsonwebtoken,dotenv
git clone https://github.com/ruslanhaibatov/financial-api-node.git
cd financial-api-node
npm install
cp .env.example .envfill in .env:
PORT=3000
NODE_ENV=development
DB_HOST=localhost
DB_PORT=5432
DB_NAME=financial_api
DB_USER=postgres
DB_PASSWORD=yourpassword
JWT_SECRET=some_long_random_string
JWT_EXPIRES_IN=7d
BCRYPT_ROUNDS=10create the database and run the schema:
psql -U postgres -c "CREATE DATABASE financial_api;"
psql -U postgres -d financial_api -f sql/schema.sql
psql -U postgres -d financial_api -f sql/seed.sql # optional test datastart:
npm start # production
npm run dev # with nodemonall protected routes require Authorization: Bearer <token>.
| method | path | auth | description |
|---|---|---|---|
| POST | /api/auth/register |
— | create account |
| POST | /api/auth/login |
— | get token |
| GET | /api/auth/me |
✓ | current user |
register
{
"username": "ruslan",
"email": "ruslan@example.com",
"password": "Password1!"
}password rules: 8+ chars, uppercase, lowercase, digit.
login
{
"email": "ruslan@example.com",
"password": "Password1!"
}both return:
{
"token": "<jwt>",
"user": { "id": "...", "username": "ruslan", "email": "ruslan@example.com" }
}| method | path | description |
|---|---|---|
| POST | /api/wallets |
create wallet |
| GET | /api/wallets |
list my wallets |
| GET | /api/wallets/:id |
single wallet |
| POST | /api/wallets/:id/deposit |
add funds |
create wallet
{ "currency": "EUR" }currency defaults to EUR.
deposit
{ "amount": 200, "note": "first deposit" }| method | path | description |
|---|---|---|
| POST | /api/transfers |
send money |
| GET | /api/transfers |
history (paginated) |
| GET | /api/transfers/:id |
single transaction |
transfer
{
"from_wallet_id": "uuid",
"to_wallet_id": "uuid",
"amount": 50,
"note": "rent"
}returns:
{
"transaction": { "id": "...", "amount": "50.00", "currency": "EUR", "status": "completed" },
"sender_wallet": { "id": "...", "balance": "150.00" },
"receiver_wallet": { "id": "...", "balance": "850.00" }
}history supports ?page=1&limit=20.
each entry has a direction field — incoming or outgoing.
users → id, username, email, password_hash, created_at
wallets → id, user_id, currency, balance (CHECK >= 0), created_at
transactions → id, sender_wallet_id, receiver_wallet_id, amount, currency, type, status, note, created_at
transfers lock both wallet rows with SELECT ... FOR UPDATE before touching balances. if anything fails, postgres rolls everything back.
sql/
schema.sql
seed.sql
src/
db/pool.js
middleware/
auth.js
errorHandler.js
routes/
auth.js
wallets.js
transfers.js
utils/
jwt.js
validators.js
index.js
made by ruslan haibatov · made by ruslan with love<3