Chat about this codebase

AI-powered code exploration

Online

Project Overview

This project delivers a full-stack pizza management app. A Node.js/TypeScript backend exposes REST APIs to handle authentication, orders and ingredients. A React Native mobile app provides role-specific interfaces for waiters and cooks, enabling real-time order workflows.

Why the project exists

  • Streamline pizza order processing from front-of-house to kitchen
  • Provide clear, role-based workflows for waiters and cooks
  • Offer a developer-friendly TypeScript codebase with RESTful APIs
  • Demonstrate full-stack integration between React Native and a Node.js backend

Key Features

  • Role-based authentication using JWTs in request headers
  • REST endpoints for:
    • Order creation, updates and status transitions
    • Ingredient inventory management
  • React Native mobile app with:
    • Waiter UI: create orders, view item statuses
    • Cook UI: see pending orders, update status to “In Progress” and “Done”
  • PostgreSQL database schema for orders, items, ingredients
  • TypeScript throughout backend and mobile code

Supported Roles

  • Waiter
    • Log in, create new orders, attach items
    • View live status updates on each order
  • Cook
    • Authenticate, fetch pending orders
    • Update orders from “Pending” → “In Progress” → “Done”
  • Admin (via API)
    • Manage ingredient catalog and stock levels
    • Update or remove ingredients

High-Level Component Diagram

      +----------------------+       REST        +-------------------------+       SQL       +--------------+
      | React Native Mobile  | <---------------> | Node.js/TypeScript API  | <----------->  | PostgreSQL   |
      |  • Waiter UI         |                   |  • AuthController       |                | Database     |
      |  • Cook UI           |                   |  • OrderController      |                |              |
      +----------------------+                   |  • IngredientController |                +--------------+
                                                 +-------------------------+
  • Mobile app communicates via HTTPS to backend
  • Backend enforces role headers, processes business logic, persists data to PostgreSQL

Getting Started

Follow these steps to clone the repo, configure and seed the local PostgreSQL database, launch the backend API, and run the React Native mobile app.

Prerequisites

  • Node.js v16+ and npm
  • PostgreSQL v12+ (or Docker)
  • Android Studio (for Android emulator) or Xcode (for iOS simulator)
  • CocoaPods (macOS, for iOS)

1. Clone the Repository

git clone https://github.com/Damjanose/pizza-management-nodeJS_ReactNative.git
cd pizza-management-nodeJS_ReactNative

2. Backend Setup

2.1 Configure Environment

In backend/, create a .env with your database URL:

cd backend
cat > .env <<EOF
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/pizza"
EOF

Or start PostgreSQL via Docker:

docker run --name pizza-db \
  -e POSTGRES_USER=postgres \
  -e POSTGRES_PASSWORD=postgres \
  -e POSTGRES_DB=pizza \
  -p 5432:5432 -d postgres

2.2 Install & Prepare

npm install
npm run db:generate     # prisma generate
npm run db:push         # prisma db push
npm run db:seed         # run seed script

To reset & reseed in one step:

npm run db:reset

2.3 Run Development Server

npm run dev
  • Watches src/**/*.ts, restarts via ts-node.
  • Default listen port: 3000
  • Health check: GET http://localhost:3000/healthz

3. Mobile Setup

3.1 Install Dependencies

cd ../mobile
npm install

For iOS only (macOS):

npx pod-install ios

3.2 Launch Metro Bundler

npm start

3.3 Run on Simulator / Device

In a new terminal:

# iOS
npm run ios

# Android (ensure emulator running or device connected)
npm run android

Notes:

  • Android: React Native maps localhost to 10.0.2.2 by default.
  • Metro config merges metro.config.js with defaults; customize as needed.

4. Verify End-to-End

  1. With backend at http://localhost:3000 and mobile app running, place a test order in the app.
  2. Confirm API logs in backend console and data persists in PostgreSQL.

You now have a full local development environment for Pizza Store Management—backend API with Prisma and a React Native mobile client.

Core Concepts & Architecture

This repository splits into two codebases:

  • Backend: Node.js + TypeScript + Express + Prisma
  • Mobile: React Native + TypeScript + React Context + AsyncStorage

Each part follows clear patterns for organization, request/response flow and state management.


Backend Architecture

1. Code Organization

backend/
├─ src/
│  ├─ controllers/       # Route handlers
│  ├─ middleware/        # errorHandler, auth checks, body parsing
│  ├─ routes/            # express.Router instances
│  ├─ utils/
│  │  └─ prisma.ts       # shared PrismaClient
│  └─ index.ts           # app setup and server start
└─ tsconfig.json

2. Core Libraries

  • Express for routing and middleware
  • @prisma/client for type-safe DB access
  • cors, helmet, morgan for security and logging

3. Prisma Client Setup

Centralize a single PrismaClient instance with environment‐sensitive logging.

// backend/src/utils/prisma.ts
import { PrismaClient } from '@prisma/client';

export const prisma = new PrismaClient({
  log: process.env.NODE_ENV === 'development'
    ? ['query','info','warn','error']
    : ['error'],
});

Usage in any module:

// backend/src/controllers/ingredient.ts
import { prisma } from '../utils/prisma';

export async function listIngredients(req, res, next) {
  try {
    const items = await prisma.ingredient.findMany();
    res.json(items);
  } catch (err) {
    next(err);
  }
}

4. Request/Response Flow

// backend/src/index.ts
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import { prisma } from './utils/prisma';
import ingredientRouter from './routes/ingredient';
import errorHandler from './middleware/errorHandler';

const app = express();

app.use(helmet());
app.use(cors());
app.use(express.json());

app.use('/ingredients', ingredientRouter);
app.use(errorHandler);

const port = process.env.PORT || 4000;
app.listen(port, () => console.log(`Listening on ${port}`));

// Graceful shutdown
process.on('SIGINT', async () => {
  await prisma.$disconnect();
  process.exit(0);
});
  • Routes register controllers
  • Error Handler catches all thrown errors and returns JSON { status, message }
  • Shutdown Hook disconnects Prisma

5. Best Practices

  • Reuse the single prisma instance; avoid new clients per request
  • Centralize error handling in one middleware
  • Store DB credentials in environment variables

Mobile Architecture

1. Code Organization

mobile/
├─ src/
│  ├─ providers/         # AuthProvider, ThemeProvider, etc.
│  ├─ services/          # API wrappers (e.g. AuthService)
│  ├─ constants/         # ROLE, API_URL
│  ├─ navigation/        # React Navigation setup
│  └─ screens/           # UI components and screens
└─ tsconfig.json

2. State Management: AuthProvider

Provides login/logout, session persistence and role checking via React Context and AsyncStorage.

// mobile/src/providers/AuthProvider.tsx
import React, { createContext, useCallback, useEffect, useState } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { AuthService } from '../services/auth';
import { ROLE } from '../constants/auth';

interface AuthContextType {
  login(name: string, pass: string): Promise<void>;
  logout(): Promise<void>;
  loading: boolean;
  isSignedIn: boolean;
  role: string;
}

export const AuthContext = createContext<AuthContextType>(null!);

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [loading, setLoading] = useState(false);
  const [isSignedIn, setIsSignedIn] = useState(false);
  const [role, setRole] = useState('');

  const login = async (name: string, pass: string) => {
    setLoading(true);
    try {
      const { data } = await AuthService.login({ name, pass });
      await AsyncStorage.setItem(ROLE, data.role);
      setRole(data.role);
      setIsSignedIn(true);
    } finally {
      setLoading(false);
    }
  };

  const logout = async () => {
    await AsyncStorage.clear();
    setIsSignedIn(false);
    setRole('');
  };

  const checkStoredRole = useCallback(async () => {
    const stored = await AsyncStorage.getItem(ROLE);
    if (stored) {
      setRole(stored);
      setIsSignedIn(true);
    }
  }, []);

  useEffect(() => { checkStoredRole(); }, [checkStoredRole]);

  return (
    <AuthContext.Provider value={{ login, logout, loading, isSignedIn, role }}>
      {children}
    </AuthContext.Provider>
  );
}
  • Wrap your root component in AuthProvider
  • Use useContext(AuthContext) to access auth state and actions

3. API Communication Flow

Centralize HTTP calls in services:

// mobile/src/services/auth.ts
import axios from 'axios';
import { API_URL } from '../constants/api';

const api = axios.create({ baseURL: API_URL });

export const AuthService = {
  login: (creds: { name: string; pass: string }) =>
    api.post('/auth/login', creds),
  // add fetchUser, refreshToken, etc.
};
  • Services throw on HTTP errors; consumed by providers or screens
  • Attach role or tokens to headers using Axios interceptors as needed

4. Navigation & Protected Screens

Leverage isSignedIn and role to guard routes:

// mobile/src/navigation/AppNavigator.tsx
import React, { useContext } from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import { AuthContext } from '../providers/AuthProvider';
import HomeScreen from '../screens/Home';
import LoginScreen from '../screens/Login';

const Stack = createStackNavigator();

export default function AppNavigator() {
  const { isSignedIn } = useContext(AuthContext);

  return (
    <Stack.Navigator>
      {isSignedIn
        ? <Stack.Screen name="Home" component={HomeScreen} />
        : <Stack.Screen name="Login" component={LoginScreen} />}
    </Stack.Navigator>
  );
}

This architecture promotes clear separation of concerns, shared state via singletons and contexts, and predictable request/response flows across the backend and mobile clients.

Database & Data Models

This section describes the Prisma schema for the pizza ordering system, illustrates model relationships, and explains how to seed the database for local development and testing.

Prisma Schema Overview

Generator & Datasource

// backend/prisma/schema.prisma

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}
  • generator client produces the Prisma Client for TypeScript queries.
  • datasource db points to your PostgreSQL instance via DATABASE_URL.

Data Models & Relations

model Order {
  id          Int                @id @default(autoincrement())
  status      OrderStatus        @default(PENDING)
  items       OrderIngredient[]  @relation("OrderToItems")
  createdAt   DateTime           @default(now())
  updatedAt   DateTime           @updatedAt
}

model Ingredient {
  id        Int                @id @default(autoincrement())
  name      String             @unique
  price     Float
  inOrders  OrderIngredient[]  @relation("IngredientToOrders")
}

model OrderIngredient {
  orderId       Int
  ingredientId  Int
  quantity      Int              @default(1)

  order         Order            @relation("OrderToItems", fields: [orderId], references: [id])
  ingredient    Ingredient       @relation("IngredientToOrders", fields: [ingredientId], references: [id])

  @@id([orderId, ingredientId])
}

enum OrderStatus {
  PENDING
  PREPARING
  COMPLETED
  CANCELLED
}
Schema Diagram (Mermaid)
erDiagram
  ORDER ||--o{ ORDER_INGREDIENT : contains
  INGREDIENT ||--o{ ORDER_INGREDIENT : used_in

  ORDER {
    Int id PK
    OrderStatus status
    DateTime createdAt
    DateTime updatedAt
  }
  INGREDIENT {
    Int id PK
    String name
    Float price
  }
  ORDER_INGREDIENT {
    Int orderId FK
    Int ingredientId FK
    Int quantity
  }

Applying Migrations & Generating Client

  1. Create and apply migration:
    npx prisma migrate dev --name init
    
  2. Generate Prisma Client:
    npx prisma generate
    

Seeding Strategy for Local Testing

Seed Script Overview

// backend/src/seed/seed.ts
import { PrismaClient, OrderStatus } from "@prisma/client";
const prisma = new PrismaClient();

async function main() {
  // Clear existing data
  await prisma.orderIngredient.deleteMany();
  await prisma.order.deleteMany();
  await prisma.ingredient.deleteMany();

  // Predefined ingredients
  const ingredientsData = [
    { name: "Mozzarella", price: 1.5 },
    { name: "Tomato Sauce", price: 0.8 },
    { name: "Pepperoni", price: 2.0 },
    { name: "Basil", price: 0.5 },
  ];
  await prisma.ingredient.createMany({ data: ingredientsData });

  // Sample orders with ingredients
  await prisma.order.create({
    data: {
      status: OrderStatus.PENDING,
      items: {
        create: [
          { ingredient: { connect: { name: "Mozzarella" } }, quantity: 2 },
          { ingredient: { connect: { name: "Tomato Sauce" } }, quantity: 1 },
        ],
      },
    },
  });

  await prisma.order.create({
    data: {
      status: OrderStatus.PREPARING,
      items: {
        create: [
          { ingredient: { connect: { name: "Pepperoni" } }, quantity: 3 },
          { ingredient: { connect: { name: "Basil" } }, quantity: 5 },
        ],
      },
    },
  });

  console.log("Database seeded with ingredients and orders.");
}

main()
  .catch(e => {
    console.error(e);
    process.exit(1);
  })
  .finally(() => prisma.$disconnect());

Running the Seed Script

Add to package.json:

{
  "scripts": {
    "seed": "ts-node --transpile-only src/seed/seed.ts"
  }
}

Execute:

npm run seed

Local Testing Workflow

  1. Ensure .env contains a valid DATABASE_URL.
  2. Run migrations: npm run prisma migrate dev
  3. Generate client: npm run prisma generate
  4. Seed data: npm run seed
  5. Start the backend server: npm start

This setup populates your local database with sample ingredients and orders, enabling rapid feature development and end-to-end testing.

Backend API Reference

Overview: This section details all public REST endpoints, their request/response schemas, authentication requirements and common error cases.

User Login Endpoint

Authenticate users by validating credentials and returning their assigned role.

Endpoint

POST /api/auth/login

Request Payload

{
  "name": "string",     // username
  "pass": "string"      // password
}

Both name and pass are required. Omitting either returns HTTP 400.

Response

Success (200):

{
  "role": "string"      // e.g. "admin", "user"
}

Error:

  • 400 { "error": "Name and password are required" }
  • 401 { "error": "Invalid credentials" }
  • 500 { "error": "Login failed" }

Implementation

Controller (src/controllers/authController.ts):

import { Request, Response } from 'express';
import { LoginRequest, LoginResponse } from '../types/auth';
import { validateCredentials } from '../middlewares/auth';

export const login = (req: Request, res: Response): void => {
  try {
    const { name, pass }: LoginRequest = req.body;
    if (!name || !pass) {
      res.status(400).json({ error: 'Name and password are required' });
      return;
    }
    const role = validateCredentials(name, pass);
    if (!role) {
      res.status(401).json({ error: 'Invalid credentials' });
      return;
    }
    res.json<LoginResponse>({ role });
  } catch {
    res.status(500).json({ error: 'Login failed' });
  }
};

Route (src/routes/authRoutes.ts):

import { Router } from 'express';
import { login } from '../controllers/authController';

const router = Router();
router.post('/login', login);
export { router as authRoutes };

Mount in server (src/index.ts):

import express from 'express';
import { authRoutes } from './routes/authRoutes';

const app = express();
app.use(express.json());
app.use('/api/auth', authRoutes);
app.listen(3000, () => console.log('Server running on port 3000'));

Client Examples

cURL:

curl -X POST http://localhost:3000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"name":"alice","pass":"secret"}'

Fetch (browser/Node):

async function login(name, pass) {
  const res = await fetch('/api/auth/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ name, pass })
  });
  if (!res.ok) {
    const { error } = await res.json();
    throw new Error(error);
  }
  const { role } = await res.json();
  return role;
}

Order Status Transitions

Confirm an order and mark it as ready using cooker-only PATCH endpoints.

Endpoints

  • PATCH /api/orders/:id/confirm → status = CONFIRMED
  • PATCH /api/orders/:id/ready → status = READY

Authorization

Middleware: validateRolerequireRole(['cooker'])

Request

  • Path parameter: id (number) – target order ID
  • No body required

Sample Fetch Usage

import { OrderResponse } from '../types/order';

async function updateStatus(orderId: number, action: 'confirm' | 'ready'): Promise<OrderResponse> {
  const res = await fetch(`/api/orders/${orderId}/${action}`, {
    method: 'PATCH',
    headers: { 'Content-Type': 'application/json' }
  });
  if (!res.ok) {
    const err = await res.json();
    throw new Error(err.error || `Failed to ${action} order`);
  }
  return res.json();
}

// Confirm order #42
updateStatus(42, 'confirm')
  .then(order => console.log('Confirmed:', order))
  .catch(console.error);

Sample Response

{
  "id": 42,
  "tableNumber": 7,
  "status": "CONFIRMED",       // or "READY"
  "createdAt": "2025-07-16T12:00:00.000Z",
  "updatedAt": "2025-07-16T12:05:00.000Z",
  "ingredients": [
    { "id": 1, "name": "Lettuce" },
    { "id": 3, "name": "Tomato" }
  ]
}

Errors

  • 400 if id is not a number
  • 401/403 if unauthenticated or wrong role
  • 500+ for server/database errors

Ingredient Endpoints

Expose operations for the ingredient catalog: list all or add new items (testing/dev).

1. List All Ingredients

GET /api/ingredients

Access: Authenticated users with role waiter or cooker

Behavior: Returns all ingredients sorted by name ascending.

Controller (src/controllers/ingredientController.ts):

export const getAllIngredients = async (req, res, next) => {
  try {
    const ingredients = await prisma.ingredient.findMany({
      orderBy: { name: 'asc' }
    });
    res.json(ingredients);
  } catch (error) {
    next(error);
  }
};

Route (src/routes/ingredientRoutes.ts):

import { Router } from 'express';
import { getAllIngredients, createIngredient } from '../controllers/ingredientController';
import { validateRole, requireRole } from '../middlewares/auth';

const router = Router();
router.use(validateRole);
router.get('/ingredients', requireRole(['waiter', 'cooker']), getAllIngredients);
router.post('/ingredients', createIngredient);
export { router as ingredientRoutes };

Example request:

curl -H "Authorization: Bearer $TOKEN" \
     https://your-api.com/api/ingredients

Example response:

[
  { "id": 1, "name": "Basil" },
  { "id": 2, "name": "Olive Oil" },
  { "id": 3, "name": "Tomato" }
]

2. Create a New Ingredient

POST /api/ingredients

Access: Any authenticated user (testing/dev)

Behavior: Validates name, trims whitespace, creates record.

Controller:

export const createIngredient = async (req, res, next) => {
  try {
    const { name } = req.body;
    if (!name || typeof name !== 'string') {
      res.status(400).json({ error: 'Ingredient name is required' });
      return;
    }
    const ingredient = await prisma.ingredient.create({
      data: { name: name.trim() }
    });
    res.status(201).json(ingredient);
  } catch (error) {
    next(error);
  }
};

Example request:

curl -X POST \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "name": "   Oregano   " }' \
  https://your-api.com/api/ingredients

Example response (201 Created):

{ "id": 4, "name": "Oregano" }

Error case (missing name):

Status: 400 Bad Request
{ "error": "Ingredient name is required" }

Practical Tips

  • Always apply express.json() before routes.
  • Prefix auth routes with /api/auth, orders with /api/orders, ingredients with /api/ingredients.
  • Use requireRole([...]) to restrict access by role.
  • Validate and trim inputs on the client to reduce invalid requests.
  • For production, restrict POST /api/ingredients to admin users.

Mobile Development Guide

This guide covers extending UI components, adding new screens, applying theming, and handling network/data updates in the React Native mobile app for pizza management.

Extending UI Components

Create reusable, styled components in src/components/. For example, an OrderCard that displays order details:

// src/components/OrderCard.tsx
import React from "react"
import { View, Text, StyleSheet, TouchableOpacity } from "react-native"
import { Order } from "../stores/useOrdersStore"

interface Props {
  order: Order
  onPress?: () => void
}

export function OrderCard({ order, onPress }: Props) {
  return (
    <TouchableOpacity style={styles.card} onPress={onPress}>
      <Text style={styles.title}>Table {order.tableNumber}</Text>
      <Text>Status: {order.status}</Text>
      <Text>Items: {order.ingredients.map(i => i.name).join(", ")}</Text>
      <Text style={styles.date}>
        {new Date(order.createdAt).toLocaleTimeString()}
      </Text>
    </TouchableOpacity>
  )
}

const styles = StyleSheet.create({
  card: {
    padding: 16,
    marginVertical: 8,
    borderRadius: 8,
    backgroundColor: "#fff",
    elevation: 2,
  },
  title: { fontSize: 18, fontWeight: "600" },
  date: { marginTop: 4, color: "#666", fontSize: 12 },
})

Import and reuse in screens or lists:

<OrderCard
  order={item}
  onPress={() => navigation.navigate("OrderDetail", { orderId: item.id })}
/>

Adding Screens

The app uses React Navigation (stack). To add a new screen:

  1. Define the screen component in src/screens/.
  2. Register it in the navigator.
// src/screens/IngredientListScreen.tsx
import React, { useEffect } from "react"
import { View, FlatList, Text } from "react-native"
import { useOrdersStore } from "../stores/useOrdersStore"
import { Ingredient } from "../stores/useOrdersStore"

export function IngredientListScreen() {
  const { ingredients, loading, error, fetchIngredients } = useOrdersStore()

  useEffect(() => {
    fetchIngredients()
  }, [])

  if (loading) return <Text>Loading ingredients…</Text>
  if (error) return <Text>Error: {error}</Text>

  return (
    <FlatList
      data={ingredients}
      keyExtractor={(item: Ingredient) => item.id.toString()}
      renderItem={({ item }) => <Text style={{ padding: 12 }}>{item.name}</Text>}
    />
  )
}
// src/navigation/AppNavigator.tsx
import React from "react"
import { createStackNavigator } from "@react-navigation/stack"
import { OrdersScreen } from "../screens/OrdersScreen"
import { OrderDetailScreen } from "../screens/OrderDetailScreen"
import { IngredientListScreen } from "../screens/IngredientListScreen"

const Stack = createStackNavigator()

export function AppNavigator() {
  return (
    <Stack.Navigator initialRouteName="Orders">
      <Stack.Screen name="Orders" component={OrdersScreen} />
      <Stack.Screen name="OrderDetail" component={OrderDetailScreen} />
      <Stack.Screen name="Ingredients" component={IngredientListScreen} />
    </Stack.Navigator>
  )
}

Theming

Centralize colors, typography, and spacing in src/theme.ts. Wrap your app with a theme provider.

// src/theme.ts
export const lightTheme = {
  colors: {
    background: "#f8f8f8",
    card: "#ffffff",
    primary: "#d35400",
    text: "#2c3e50",
    border: "#ecf0f1",
  },
  spacing: {
    small: 8,
    medium: 16,
    large: 24,
  },
}
// App.tsx
import React from "react"
import { NavigationContainer, DefaultTheme } from "@react-navigation/native"
import { ThemeProvider } from "styled-components/native"
import { AppNavigator } from "./src/navigation/AppNavigator"
import { lightTheme } from "./src/theme"

const navTheme = {
  ...DefaultTheme,
  colors: { ...DefaultTheme.colors, background: lightTheme.colors.background },
}

export default function App() {
  return (
    <ThemeProvider theme={lightTheme}>
      <NavigationContainer theme={navTheme}>
        <AppNavigator />
      </NavigationContainer>
    </ThemeProvider>
  )
}

Access theme in styled components or via hook:

import styled, { useTheme } from "styled-components/native"

const Title = styled.Text`
  color: ${props => props.theme.colors.primary};
  font-size: 20px;
`

function Header() {
  const theme = useTheme()
  return <Title>Welcome to Pizza Manager</Title>
}

Handling Network & Data Updates

Leverage the useOrdersStore Zustand store (src/stores/useOrdersStore.ts) for all CRUD operations and reactive state.

  • Fetch on mount:
    useEffect(() => {
      fetchOrders()
      fetchIngredients()
    }, [])
    
  • Create / Edit:
    createOrder(tableNumber, ingredientIds)
    editOrder(orderId, newTable, newIngredientIds)
    
  • Update status:
    readyOrder(orderId)
    confirmOrder(orderId)
    
  • Add ingredient:
    addIngredient("Mushrooms")
    

Guard UI actions with loading and display error using your ErrorBanner or retry logic. For non-reactive callbacks (e.g., background timers), use useOrdersStore.getState().fetchOrders(). Batch initial loads in a single effect to minimize UI flicker.

Deployment & Release

This section covers building the production backend Docker image, running database migrations, configuring environment variables, and generating signed mobile releases for Android and iOS.

Backend: Docker Image

Create a multi-stage Dockerfile in backend/:

# backend/Dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json tsconfig.json ./
COPY prisma ./prisma
RUN npm ci
COPY src ./src
RUN npm run build

FROM node:18-alpine AS runner
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY prisma ./prisma
COPY .env .env
EXPOSE 3000
CMD ["npm", "start"]

Build and run:

cd backend
docker build -t pizza-backend:latest .
docker run -d \
  --name pizza-backend \
  --env-file .env \
  -p 3000:3000 \
  pizza-backend:latest

Database Migrations

Ensure your .env contains DATABASE_URL. Then:

# Generate Prisma client
npm run db:generate

# Development migration (creates/updates migration files)
npm run db:migrate

# Push schema to database without migration history
npm run db:push

# Production: apply all pending migrations
npx prisma migrate deploy

Environment Variables

Create a backend/.env file:

DATABASE_URL=postgresql://user:pass@host:5432/pizza_db
JWT_SECRET=your_jwt_secret
PORT=3000

Load them in Docker or CI via --env-file .env.

Android: Signed Release

  1. Place your keystore at mobile/android/app/keystores/release.keystore.

  2. In mobile/android/gradle.properties:

    MYAPP_RELEASE_STORE_FILE=keystores/release.keystore
    MYAPP_RELEASE_KEY_ALIAS=release_key
    MYAPP_RELEASE_STORE_PASSWORD=store_password
    MYAPP_RELEASE_KEY_PASSWORD=key_password
    
  3. Verify android/app/build.gradle uses these props:

    signingConfigs {
      release {
        storeFile file(MYAPP_RELEASE_STORE_FILE)
        storePassword MYAPP_RELEASE_STORE_PASSWORD
        keyAlias MYAPP_RELEASE_KEY_ALIAS
        keyPassword MYAPP_RELEASE_KEY_PASSWORD
      }
    }
    buildTypes {
      release {
        signingConfig signingConfigs.release
        minifyEnabled false
      }
    }
    
  4. Build the APK or AAB:

    cd mobile/android
    ./gradlew assembleRelease         # APK: app/build/outputs/apk/release/app-release.apk
    ./gradlew bundleRelease           # AAB: app/build/outputs/bundle/release/app-release.aab
    

iOS: Signed Release

  1. Open mobile/ios/ReactNativeNewsApp.xcodeproj (or .xcworkspace) in Xcode.

  2. Under Signing & Capabilities, set your Team, Provisioning Profile, and Bundle Identifier.

  3. Create exportOptions.plist at mobile/ios/exportOptions.plist:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
      <dict>
        <key>method</key>
        <string>app-store</string>
        <key>teamID</key>
        <string>YOUR_TEAM_ID</string>
      </dict>
    </plist>
    
  4. Archive and export:

    cd mobile/ios
    xcodebuild -workspace ReactNativeNewsApp.xcworkspace \
      -scheme ReactNativeNewsApp \
      -configuration Release \
      -archivePath build/ReactNativeNewsApp.xcarchive \
      archive
    
    xcodebuild -exportArchive \
      -archivePath build/ReactNativeNewsApp.xcarchive \
      -exportOptionsPlist exportOptions.plist \
      -exportPath build/ReactNativeNewsApp.ipa
    

The generated .ipa (build/ReactNativeNewsApp.ipa) is ready for App Store upload.

Contribution Guide & Development Workflow

This guide covers setting up linting, testing, pull‐request workflow, and commit conventions for Damjanose/pizza-management--nodeJS_ReactNative.

Prerequisites

  • Node.js ≥ 14, npm or Yarn
  • Android Studio / Xcode for mobile
  • Git client

Local Setup

  1. Fork and clone the repo:
    git clone git@github.com:<your-username>/pizza-management--nodeJS_ReactNative.git
    cd pizza-management--nodeJS_ReactNative
    
  2. Install dependencies:
    # Backend
    cd backend
    npm install
    
    # Mobile
    cd ../mobile
    npm install
    
  3. Copy and configure environment variables (.env):
    cp backend/.env.example backend/.env
    cp mobile/.env.example mobile/.env
    # Edit values as needed
    

Linting

Backend

Configure ESLint by extending your preferred rules in backend/.eslintrc.js. Run:

cd backend
npm run lint        # check
npm run lint:fix    # auto‐fix (if configured)

Mobile

mobile/package.json includes lint scripts:

cd mobile
npm run lint        # ESLint + Prettier checks
npm run lint:fix    # auto‐fix

Testing

Mobile

Jest uses mobile/jest.config.js. Run snapshot and functional tests:

cd mobile
npm test            # runs all tests

Example snapshot test (mobile/__tests__/App.test.tsx):

import renderer from 'react-test-renderer';
import App from '../App';

test('App renders correctly', () => {
  const tree = renderer.create(<App />).toJSON();
  expect(tree).toMatchSnapshot();
});

Backend

Add Jest or your preferred framework as needed. No built-in tests currently.

Development Scripts

Backend (backend/package.json)

  • npm run dev — start server in watch mode
  • npm run build — compile TypeScript to dist/
  • npm start — run compiled build
  • npm run db:push / db:reset / db:seed — Prisma migrations and seeding

Mobile (mobile/package.json)

  • npm run ios / npm run android — launch simulator/device
  • npm run start — Metro bundler
  • npm run test — run Jest
  • npm run lint / lint:fix — ESLint + Prettier checks

Branch & Pull‐Request Workflow

  1. Create feature branch:
    git checkout -b feature/123-add-pizza-crud
    
  2. Develop and commit changes.
  3. Push and open PR against main:
    git push origin feature/123-add-pizza-crud
    
  4. Ensure:
    • All lint checks pass (npm run lint)
    • Mobile tests pass (npm test)
    • PR description links to issue and outlines changes
  5. Address review comments, then merge.

Commit Conventions (Conventional Commits)

Use <type>(<scope>): <subject>
Scopes can be backend, mobile, api, etc.

Types:

  • feat — new feature
  • fix — bug fix
  • docs — documentation only
  • style — formatting, no code logic change
  • refactor — code change without feature or fix
  • test — adding or updating tests
  • chore — build process or auxiliary tool changes

Example:

git commit -m "feat(backend): add Prisma database seeder"

Adhering to these guidelines ensures consistent code quality, reliable builds, and clear collaboration.