Skip to main content

Overview

The Crypto Shop Backend uses standard HTTP status codes and consistent error response formats. This page documents all error codes, their meanings, and how to handle them in your frontend application.

Error Response Format

All errors follow a consistent JSON structure:
{
  "error": "Human-readable error message",
  "code": "MACHINE_READABLE_ERROR_CODE",
  "details": {
    // Additional context-specific information
  }
}

Standard Error Response

error
string
required
Human-readable error message describing what went wrong
code
string
Machine-readable error code for programmatic handling (optional)
details
object
Additional context-specific information (optional)

HTTP Status Codes

400 - Bad Request

The request is malformed or contains invalid data. Common Scenarios:

Missing Required Fields

{
  "error": "Email and password are required"
}

Validation Errors

{
  "errors": [
    {
      "msg": "Password must be at least 8 characters",
      "param": "password",
      "location": "body"
    }
  ]
}

Insufficient Stock

{
  "error": "Insufficient stock for Crypto Hardware Wallet"
}

Insufficient Balance

{
  "error": "Insufficient balance",
  "code": "INSUFFICIENT_BALANCE",
  "userBalance": 50.0,
  "requiredAmount": 99.98
}
Error Code: INSUFFICIENT_BALANCE Handling:
if (error.code === 'INSUFFICIENT_BALANCE') {
  const needed = error.requiredAmount - error.userBalance;
  alert(`You need ${needed.toFixed(2)} more TRX to complete this purchase.`);
}

Order Not Pending

{
  "error": "Order is not pending"
}

Wallet Not Found

{
  "error": "User wallet not found"
}

401 - Unauthorized

Authentication is required or has failed.

No Token Provided

{
  "error": "No token, authorization denied"
}
Location: src/middlewares/auth.js:8 Handling:
// Redirect to login page
if (error.status === 401) {
  window.location.href = '/login';
}

Invalid or Expired Token

{
  "error": "Token is invalid or expired"
}
Location: src/middlewares/auth.js:14 Handling:
// Attempt token refresh
if (error.error === 'Token is invalid or expired') {
  await refreshAccessToken();
  // Retry original request
}

Invalid Credentials

{
  "error": "Invalid email or password"
}
Location: src/api/auth/login.js:22

Account Inactive

{
  "error": "User account is inactive"
}
Location: src/api/auth/login.js:26

403 - Forbidden

Authentication succeeded but user lacks permission.

Admin Access Required

{
  "error": "Admin access only"
}
Location: src/middlewares/auth.js:25 Handling:
if (error.status === 403) {
  alert('You do not have permission to access this resource.');
  router.push('/dashboard');
}

User Access Required

{
  "error": "User access only"
}
Location: src/middlewares/auth.js:32

Unauthorized Access to Order

{
  "error": "Unauthorized"
}
Location: src/api/orders/payOrder.js:18 Returned when a user attempts to access or pay for another user’s order.

404 - Not Found

The requested resource does not exist.

Order Not Found

{
  "error": "Order not found"
}
Location: src/api/orders/payOrder.js:14

Product Not Found

{
  "error": "Product 507f1f77bcf86cd799439013 not found"
}
Location: src/api/orders/createOrder.js:34 Handling:
if (error.status === 404) {
  alert('The requested resource was not found.');
}

409 - Conflict

The request conflicts with the current state of the server.

Pending Order Exists

{
  "error": "You have a pending order. Please complete or cancel it first.",
  "code": "PENDING_ORDER_EXISTS",
  "pendingOrderId": "507f1f77bcf86cd799439012"
}
Error Code: PENDING_ORDER_EXISTS Location: src/api/orders/createOrder.js:16-20 Handling:
if (error.code === 'PENDING_ORDER_EXISTS') {
  const viewOrder = confirm('You have a pending order. Would you like to view it?');
  if (viewOrder) {
    router.push(`/orders/${error.pendingOrderId}`);
  }
}

Duplicate User

{
  "error": "Email already exists"
}
Returned when attempting to register with an existing email or username.

500 - Internal Server Error

An unexpected error occurred on the server.

Generic Server Error

{
  "error": "Internal server error message"
}
Handling:
if (error.status === 500) {
  alert('An unexpected error occurred. Please try again later.');
  // Log error to monitoring service
  logErrorToSentry(error);
}

Merchant Wallet Not Configured

{
  "error": "Merchant wallet not configured"
}
Location: src/api/orders/createOrder.js:54 This indicates a server configuration issue. Contact support.

Transaction Failed

{
  "error": "Transaction broadcast failed: [blockchain error message]"
}
Location: src/api/orders/payOrder.js:80-83 Returned when the blockchain transaction fails. The order status is automatically set to failed.

Application-Specific Error Codes

Authentication & Authorization

CodeStatusDescriptionLocation
INVALID_CREDENTIALS401Email or password is incorrectauth/login.js
ACCOUNT_INACTIVE401User account has been deactivatedauth/login.js
TOKEN_EXPIRED401Access token has expiredmiddlewares/auth.js
ADMIN_ONLY403Endpoint requires admin rolemiddlewares/auth.js

Orders & Payments

CodeStatusDescriptionLocation
PENDING_ORDER_EXISTS409User has an existing pending orderorders/createOrder.js:18
INSUFFICIENT_BALANCE400Wallet balance too low for paymentorders/payOrder.js:35
ORDER_NOT_PENDING400Order cannot be paid (already completed/cancelled)orders/payOrder.js:22
INSUFFICIENT_STOCK400Product out of stockorders/createOrder.js:36

Products

CodeStatusDescription
PRODUCT_NOT_FOUND404Product does not exist
PRODUCT_INACTIVE400Product is not available for purchase

Validation Errors

Validation errors use a different format from express-validator:
{
  "errors": [
    {
      "msg": "Password must be at least 8 characters",
      "param": "password",
      "location": "body"
    },
    {
      "msg": "Passwords do not match",
      "param": "passwordConfirm",
      "location": "body"
    }
  ]
}
Location: src/middlewares/validation.js Handling:
if (error.errors && Array.isArray(error.errors)) {
  error.errors.forEach(err => {
    showFieldError(err.param, err.msg);
  });
}

Common Validation Rules

Registration (validateRegister)

  • Email must be valid
  • Username minimum 3 characters
  • Password minimum 8 characters
  • Password confirmation must match

Login (validateLogin)

  • Email must be valid
  • Password required

Send TRX (validateSendTRX)

  • Address required
  • Amount minimum 0.001 TRX

Frontend Error Handling Example

Axios Interceptor

import axios from 'axios';

const api = axios.create({
  baseURL: 'http://localhost:3000/api',
  withCredentials: true
});

// Response interceptor for error handling
api.interceptors.response.use(
  (response) => response,
  async (error) => {
    const { status, data } = error.response || {};

    // Handle specific error codes
    switch (data?.code) {
      case 'PENDING_ORDER_EXISTS':
        handlePendingOrder(data.pendingOrderId);
        break;
        
      case 'INSUFFICIENT_BALANCE':
        handleInsufficientBalance(data);
        break;
        
      case 'TOKEN_EXPIRED':
        await refreshToken();
        return api.request(error.config); // Retry request
    }

    // Handle status codes
    switch (status) {
      case 401:
        handleUnauthorized();
        break;
        
      case 403:
        handleForbidden();
        break;
        
      case 500:
        handleServerError(data.error);
        break;
    }

    return Promise.reject(error);
  }
);

export default api;

React Hook for Error Handling

import { useState } from 'react';

export function useApiError() {
  const [error, setError] = useState(null);

  const handleError = (err) => {
    const errorData = err.response?.data;
    const status = err.response?.status;

    // Format error for display
    const formattedError = {
      message: errorData?.error || 'An unexpected error occurred',
      code: errorData?.code,
      status,
      details: errorData
    };

    setError(formattedError);

    // Auto-clear after 5 seconds
    setTimeout(() => setError(null), 5000);
  };

  const clearError = () => setError(null);

  return { error, handleError, clearError };
}
Usage:
function CheckoutPage() {
  const { error, handleError, clearError } = useApiError();

  const handlePayment = async () => {
    try {
      await api.post(`/orders/${orderId}/pay`);
    } catch (err) {
      handleError(err);
    }
  };

  return (
    <div>
      {error && (
        <Alert variant="error">
          {error.message}
          {error.code === 'INSUFFICIENT_BALANCE' && (
            <p>Balance: {error.details.userBalance} TRX</p>
          )}
        </Alert>
      )}
      <button onClick={handlePayment}>Pay Now</button>
    </div>
  );
}

Best Practices

Check Error Codes

Always check for application-specific error codes before falling back to status codes

User-Friendly Messages

Display friendly messages to users, not raw error responses

Log Errors

Send 500 errors to monitoring services for investigation

Retry Logic

Implement retry logic for transient errors (network, timeouts)

Error Monitoring

Integrate error tracking for production:
import * as Sentry from '@sentry/browser';

api.interceptors.response.use(
  (response) => response,
  (error) => {
    // Log 500 errors to Sentry
    if (error.response?.status >= 500) {
      Sentry.captureException(error, {
        tags: {
          endpoint: error.config.url,
          method: error.config.method
        },
        extra: {
          response: error.response?.data
        }
      });
    }
    return Promise.reject(error);
  }
);

Testing Error Handling

Test error scenarios in your frontend:
import { render, screen } from '@testing-library/react';
import { rest } from 'msw';
import { setupServer } from 'msw/node';

const server = setupServer(
  rest.post('/api/orders', (req, res, ctx) => {
    return res(
      ctx.status(409),
      ctx.json({
        error: 'You have a pending order',
        code: 'PENDING_ORDER_EXISTS',
        pendingOrderId: '123'
      })
    );
  })
);

beforeAll(() => server.listen());
afterAll(() => server.close());

test('displays pending order error', async () => {
  render(<CheckoutPage />);
  
  await userEvent.click(screen.getByText('Create Order'));
  
  expect(screen.getByText(/pending order/i)).toBeInTheDocument();
});

Build docs developers (and LLMs) love