Vinted’s CEO says the US is an “enormous opportunity” as the $9B secondhand marketplace plots its Atlantic crossing
Back to Tutorials
techTutorialintermediate

Vinted’s CEO says the US is an “enormous opportunity” as the $9B secondhand marketplace plots its Atlantic crossing

June 9, 20264 views6 min read

Learn to build a scalable marketplace API with Node.js and Express, covering user management, product listings, and transaction handling - essential components for platforms like Vinted.

Introduction

In this tutorial, we'll explore how to build a scalable marketplace API using Node.js and Express that can handle the kind of growth that companies like Vinted are experiencing. We'll focus on creating a robust backend system that can support product listings, user management, and transaction processing - core components of any secondhand marketplace platform. This tutorial will teach you how to structure a modern API, implement database relationships, and handle common marketplace operations.

Prerequisites

  • Basic understanding of JavaScript and Node.js
  • Node.js installed (version 14 or higher)
  • npm or yarn package manager
  • Basic knowledge of RESTful API design
  • SQLite or PostgreSQL database installed
  • Postman or similar API testing tool

Step 1: Initialize Project and Install Dependencies

Why this step matters

We start by creating a clean project structure and installing all necessary packages. This sets up our foundation for building a scalable marketplace API.

mkdir vinted-api
 cd vinted-api
 npm init -y
 npm install express cors helmet morgan dotenv
 npm install -D nodemon

Step 2: Create Basic Server Structure

Why this step matters

Setting up the basic server configuration with middleware ensures our API is secure, logs requests properly, and handles cross-origin requests.

const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');
require('dotenv').config();

const app = express();
const PORT = process.env.PORT || 3000;

// Middleware
app.use(helmet());
app.use(cors());
app.use(morgan('combined'));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Routes will go here

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

Step 3: Set Up Database Models

Why this step matters

Marketplace platforms require robust data models for users, products, and transactions. We'll create a basic database structure that supports these core entities.

const sqlite3 = require('sqlite3').verbose();
const db = new sqlite3.Database(':memory:');

// Create users table
const createUserTable = `CREATE TABLE IF NOT EXISTS users (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  username TEXT UNIQUE NOT NULL,
  email TEXT UNIQUE NOT NULL,
  password_hash TEXT NOT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`;

// Create products table
const createProductTable = `CREATE TABLE IF NOT EXISTS products (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  title TEXT NOT NULL,
  description TEXT,
  price REAL NOT NULL,
  user_id INTEGER,
  category TEXT,
  condition TEXT,
  status TEXT DEFAULT 'available',
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (user_id) REFERENCES users (id)
)`;

// Execute table creation
db.serialize(() => {
  db.run(createUserTable);
  db.run(createProductTable);
});

Step 4: Implement User Authentication Routes

Why this step matters

Secure user authentication is crucial for any marketplace platform. We'll implement basic login and registration endpoints with proper error handling.

const bcrypt = require('bcrypt');

// User registration route
app.post('/api/register', async (req, res) => {
  try {
    const { username, email, password } = req.body;
    
    // Hash password
    const hashedPassword = await bcrypt.hash(password, 10);
    
    // Insert user into database
    const stmt = db.prepare("INSERT INTO users (username, email, password_hash) VALUES (?, ?, ?)");
    stmt.run(username, email, hashedPassword);
    stmt.finalize();
    
    res.status(201).json({ message: 'User created successfully' });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// User login route
app.post('/api/login', async (req, res) => {
  try {
    const { email, password } = req.body;
    
    // Find user
    db.get("SELECT * FROM users WHERE email = ?", [email], async (err, user) => {
      if (err) return res.status(500).json({ error: err.message });
      
      if (!user) return res.status(401).json({ error: 'Invalid credentials' });
      
      // Verify password
      const isValid = await bcrypt.compare(password, user.password_hash);
      if (!isValid) return res.status(401).json({ error: 'Invalid credentials' });
      
      // In a real app, we would generate a JWT token here
      res.json({ message: 'Login successful', user: { id: user.id, username: user.username } });
    });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

Step 5: Create Product Management Endpoints

Why this step matters

Product listings are the core of any marketplace. We'll implement CRUD operations for products with proper validation and user association.

// Create product
app.post('/api/products', (req, res) => {
  try {
    const { title, description, price, category, condition, user_id } = req.body;
    
    const stmt = db.prepare("INSERT INTO products (title, description, price, category, condition, user_id) VALUES (?, ?, ?, ?, ?, ?)");
    stmt.run(title, description, price, category, condition, user_id);
    stmt.finalize();
    
    res.status(201).json({ message: 'Product created successfully' });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// Get all products
app.get('/api/products', (req, res) => {
  try {
    const { page = 1, limit = 10, category, status } = req.query;
    
    let query = 'SELECT * FROM products WHERE status = ?';
    let params = ['available'];
    
    if (category) {
      query += ' AND category = ?';
      params.push(category);
    }
    
    query += ' ORDER BY created_at DESC LIMIT ? OFFSET ?';
    params.push(limit, (page - 1) * limit);
    
    db.all(query, params, (err, rows) => {
      if (err) return res.status(500).json({ error: err.message });
      res.json(rows);
    });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

Step 6: Add Transaction Handling

Why this step matters

Marketplace platforms need to handle transactions securely. We'll implement basic transaction logic with proper status tracking.

// Create transaction
app.post('/api/transactions', (req, res) => {
  try {
    const { product_id, buyer_id, seller_id, amount } = req.body;
    
    // Create transaction record
    const stmt = db.prepare("INSERT INTO transactions (product_id, buyer_id, seller_id, amount, status) VALUES (?, ?, ?, ?, ?)");
    stmt.run(product_id, buyer_id, seller_id, amount, 'pending');
    stmt.finalize();
    
    // Update product status
    db.run("UPDATE products SET status = 'sold' WHERE id = ?", [product_id]);
    
    res.status(201).json({ message: 'Transaction created successfully' });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// Get user transactions
app.get('/api/transactions/user/:user_id', (req, res) => {
  try {
    const { user_id } = req.params;
    
    db.all("SELECT * FROM transactions WHERE buyer_id = ? OR seller_id = ? ORDER BY created_at DESC", [user_id, user_id], (err, rows) => {
      if (err) return res.status(500).json({ error: err.message });
      res.json(rows);
    });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

Step 7: Test Your API

Why this step matters

Testing ensures our API works correctly and handles various scenarios. We'll use Postman to test our endpoints.

  1. Start your server: npm run dev
  2. Use Postman to test the following endpoints:
    • POST /api/register - Test user registration
    • POST /api/login - Test user authentication
    • POST /api/products - Test product creation
    • GET /api/products - Test product listing
    • POST /api/transactions - Test transaction creation

Summary

In this tutorial, we've built a foundational marketplace API with core features like user management, product listings, and transaction handling. While this is a simplified version, it demonstrates the key architectural patterns used in platforms like Vinted. The code structure we've implemented can be extended with features like JWT authentication, payment processing, image uploads, and real-time notifications to support the scale and complexity of a growing marketplace platform.

Remember that real-world applications would require additional security measures, database optimization, caching, and more sophisticated error handling. This tutorial provides the essential building blocks for understanding how marketplace platforms scale and handle their core operations.

Source: TNW Neural

Related Articles