Skip to content

Request validation ​

Every route validates its inputs with zod before reaching a service.

js
router.post(
  '/',
  requireAuth,
  validate({ body: createOrderBody }),
  asyncHandler(async (req, res) => {
    res.status(201).json(await orders.createOrder(req.user, req.valid.body));
  }),
);

validate({ params, query, body }) parses each part and puts the parsed result on req.valid. Handlers read req.valid.*, never raw req.body, so transformations (trimming, lowercasing, coercion, de-duplication) always apply.

Shared schemas ​

src/validators/common.js:

SchemaRules
emailtrimmed, lowercased, valid email, ≤ 254 chars
password8–128 chars, at least one letter and one digit
xlmAmountstring or number → string, ≤ 7 decimals, > 0
ngnRatestring or number → string, ≤ 2 decimals, > 0
stellarAddressvalid ed25519 public key (G…)
paymentMethodone of the five supported methods
pageQuerypage ≥ 1, 1 ≤ pageSize ≤ 100, coerced from strings

Area schemas ​

FileSchemas
auth.jsregister, login, verify email, forgot/reset/change password
users.jsprofile update, payout account, Nigerian phone normalisation
kyc.jsID type, 11-digit number, names, date of birth (18+)
wallet.jswithdrawal (memo ≤ 28 bytes), activity cursor
orders.jscreate order, market filters, my orders
trades.jsopen trade, my trades scope, chat message
admin.jsuser filters, status change, KYC decision, trade filters

Error format ​

Validation failures return 400 VALIDATION_ERROR with the first issue as message and every issue in details:

json
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Password must contain a number",
    "details": [{ "path": "password", "message": "Password must contain a number" }]
  }
}

The client's fieldErrors() helper maps details onto form fields.

Nexlm is a peer-to-peer facilitator and never holds customer Naira.