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 viats-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
to10.0.2.2
by default. - Metro config merges
metro.config.js
with defaults; customize as needed.
4. Verify End-to-End
- With backend at
http://localhost:3000
and mobile app running, place a test order in the app. - 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 viaDATABASE_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
- Create and apply migration:
npx prisma migrate dev --name init
- 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
- Ensure
.env
contains a validDATABASE_URL
. - Run migrations:
npm run prisma migrate dev
- Generate client:
npm run prisma generate
- Seed data:
npm run seed
- 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: validateRole
→ requireRole(['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:
- Define the screen component in
src/screens/
. - 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
Place your keystore at
mobile/android/app/keystores/release.keystore
.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
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 } }
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
Open
mobile/ios/ReactNativeNewsApp.xcodeproj
(or.xcworkspace
) in Xcode.Under Signing & Capabilities, set your Team, Provisioning Profile, and Bundle Identifier.
Create
exportOptions.plist
atmobile/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>
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
- Fork and clone the repo:
git clone git@github.com:<your-username>/pizza-management--nodeJS_ReactNative.git cd pizza-management--nodeJS_ReactNative
- Install dependencies:
# Backend cd backend npm install # Mobile cd ../mobile npm install
- 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 modenpm run build
— compile TypeScript todist/
npm start
— run compiled buildnpm run db:push
/db:reset
/db:seed
— Prisma migrations and seeding
Mobile (mobile/package.json
)
npm run ios
/npm run android
— launch simulator/devicenpm run start
— Metro bundlernpm run test
— run Jestnpm run lint
/lint:fix
— ESLint + Prettier checks
Branch & Pull‐Request Workflow
- Create feature branch:
git checkout -b feature/123-add-pizza-crud
- Develop and commit changes.
- Push and open PR against
main
:git push origin feature/123-add-pizza-crud
- Ensure:
- All lint checks pass (
npm run lint
) - Mobile tests pass (
npm test
) - PR description links to issue and outlines changes
- All lint checks pass (
- Address review comments, then merge.
Commit Conventions (Conventional Commits)
Use <type>(<scope>): <subject>
Scopes can be backend
, mobile
, api
, etc.
Types:
feat
— new featurefix
— bug fixdocs
— documentation onlystyle
— formatting, no code logic changerefactor
— code change without feature or fixtest
— adding or updating testschore
— 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.