A RESTful digital menu API built with Node.js, Express, and PostgreSQL. Businesses can register, manage their menu, and share it publicly via a unique slug — perfect for QR code menus.
- Runtime: Node.js
- Framework: Express
- Database: PostgreSQL
- Authentication: JWT
- Password hashing: bcryptjs
- Rate limiting: express-rate-limit
menu-api/
├── src/
│ ├── config/
│ │ └── db.js
│ ├── middleware/
│ │ ├── auth.js
│ │ └── rateLimiter.js
│ ├── routes/
│ │ ├── auth.routes.js
│ │ ├── menu.routes.js
│ │ └── public.routes.js
│ ├── controllers/
│ │ ├── auth.controller.js
│ │ ├── menu.controller.js
│ │ └── public.controller.js
│ ├── models/
│ │ └── db.sql
│ └── app.js
└── server.js
- Node.js v18+
- PostgreSQL
- Clone the repository
git clone https://github.com/Ramiro-9/menu-api.git
cd menu-api- Install dependencies
npm install- Set up environment variables
cp .env.example .envFill in your values in .env.
- Create the database and run the schema
CREATE DATABASE menudb;Then run the contents of src/models/db.sql in your database.
- Start the server
npm run devPORT=3000
DATABASE_URL=postgresql://user:password@localhost:5432/menudb
JWT_SECRET=your_secret_key
JWT_EXPIRES_IN=7d
RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_MAX=100
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /api/auth/register |
❌ | Register a new business |
| POST | /api/auth/login |
❌ | Login and receive JWT |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /api/menu/categories |
✅ | Get all categories |
| POST | /api/menu/categories |
✅ | Create a category |
| DELETE | /api/menu/categories/:id |
✅ | Delete a category |
| GET | /api/menu/products |
✅ | Get all products |
| POST | /api/menu/products |
✅ | Create a product |
| PUT | /api/menu/products/:id |
✅ | Update a product |
| DELETE | /api/menu/products/:id |
✅ | Delete a product |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /api/public/:slug |
❌ | Get full menu by business slug |
POST /api/auth/register
Content-Type: application/json
{
"name": "Pizzería Don Mario",
"slug": "pizzeria-don-mario",
"email": "donmario@gmail.com",
"password": "123456"
}GET /api/public/pizzeria-don-marioPOST /api/menu/categories
Authorization: Bearer <token>
Content-Type: application/json
{
"name": "Pizzas",
"order_index": 1
}POST /api/menu/products
Authorization: Bearer <token>
Content-Type: application/json
{
"category_id": 1,
"name": "Pizza Muzzarella",
"description": "Salsa de tomate, muzzarella y orégano",
"price": 2500,
"image_url": "https://via.placeholder.com/300"
}CREATE TABLE businesses (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
slug VARCHAR(255) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE categories (
id SERIAL PRIMARY KEY,
business_id INTEGER REFERENCES businesses(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
order_index INTEGER DEFAULT 0
);
CREATE TABLE products (
id SERIAL PRIMARY KEY,
category_id INTEGER REFERENCES categories(id) ON DELETE CASCADE,
business_id INTEGER REFERENCES businesses(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
description TEXT,
price NUMERIC(10, 2) NOT NULL,
image_url TEXT,
available BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT NOW()
);MIT