React Native Expo Event Management

1

Part 1: Introduction & Architecture

2

Part 2: Installation on Mac, Windows, Ubuntu

3

Part 3: Project Setup

4

Part 4: Admin Authentication

5

Part 5: Event Management

6

Part 6: QR Scanner for Attendance

7

Part 7: Polish & Deploy

This article is available in Indonesian

๐Ÿ‡ฎ๐Ÿ‡ฉ Baca dalam Bahasa Indonesia

January 21, 2026

๐Ÿ‡ฌ๐Ÿ‡ง English

React Native Expo Event Management Part 3: Project Setup

Set up a React Native Expo project with TypeScript, NativeWind styling, folder structure, and base API configuration.

4 min read


๐Ÿ“– Overview

In this part, we'll set up our Expo project with:

  • Expo Router for navigation
  • NativeWind (Tailwind CSS) for styling
  • Folder structure for scalable development
  • Base API configuration

๐Ÿš€ Step 1: Create Expo Project

# Create new Expo project with tabs template
npx create-expo-app@latest lampung-dev-mobile --template tabs

# Navigate to project
cd lampung-dev-mobile

๐Ÿ“ฆ Step 2: Install Dependencies

# Core dependencies
npm install axios zustand

# Expo packages
npx expo install expo-secure-store expo-camera

# Styling (NativeWind - Tailwind for React Native)
npm install nativewind
npm install --save-dev [email protected]

# Icons
npm install lucide-react-native
npm install react-native-svg

๐ŸŽจ Step 3: Configure NativeWind

A. Initialize Tailwind

npx tailwindcss init

B. Configure tailwind.config.js

// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./app/**/*.{js,jsx,ts,tsx}",
    "./components/**/*.{js,jsx,ts,tsx}"
  ],
  presets: [require("nativewind/preset")],
  theme: {
    extend: {
      colors: {
        primary: '#f0880a',
        secondary: '#1a1a1a',
      },
    },
  },
  plugins: [],
}

C. Create global.css

/* global.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

D. Update babel.config.js

// babel.config.js
module.exports = function (api) {
  api.cache(true);
  return {
    presets: [
      ["babel-preset-expo", { jsxImportSource: "nativewind" }],
      "nativewind/babel",
    ],
  };
};

E. Create nativewind-env.d.ts

// nativewind-env.d.ts
/// <reference types="nativewind/types" />

F. Update metro.config.js

// metro.config.js
const { getDefaultConfig } = require("expo/metro-config");
const { withNativeWind } = require('nativewind/metro');

const config = getDefaultConfig(__dirname);

module.exports = withNativeWind(config, { input: './global.css' });

๐Ÿ“‚ Step 4: Create Folder Structure

# Create folders
mkdir -p services store components constants utils

Final structure:

lampung-dev-mobile/ โ”œโ”€โ”€ app/ # Screens (Expo Router) โ”‚ โ”œโ”€โ”€ (tabs)/ # Tab navigation โ”‚ โ”œโ”€โ”€ event/ # Event screens โ”‚ โ”œโ”€โ”€ login.tsx # Login screen โ”‚ โ””โ”€โ”€ _layout.tsx # Root layout โ”‚ โ”œโ”€โ”€ components/ # Reusable components โ”œโ”€โ”€ services/ # API services โ”œโ”€โ”€ store/ # State management โ”œโ”€โ”€ constants/ # App constants โ”œโ”€โ”€ utils/ # Utilities โ””โ”€โ”€ assets/ # Images, fonts

๐Ÿ”ง Step 5: Configure app.json

{
  "expo": {
    "name": "Lampung Dev",
    "slug": "lampung-dev",
    "version": "1.0.0",
    "orientation": "portrait",
    "icon": "./assets/images/icon.png",
    "scheme": "lampungdev",
    "userInterfaceStyle": "automatic",
    "splash": {
      "image": "./assets/images/splash-icon.png",
      "resizeMode": "contain",
      "backgroundColor": "#1a1a1a"
    },
    "ios": {
      "supportsTablet": true,
      "bundleIdentifier": "org.lampungdev.mobile"
    },
    "android": {
      "adaptiveIcon": {
        "foregroundImage": "./assets/images/adaptive-icon.png",
        "backgroundColor": "#1a1a1a"
      },
      "package": "org.lampungdev.mobile",
      "permissions": ["CAMERA"]
    },
    "plugins": [
      "expo-router",
      [
        "expo-camera",
        {
          "cameraPermission": "Allow $(PRODUCT_NAME) to access camera for QR scanning"
        }
      ]
    ]
  }
}

๐ŸŒ Step 6: Setup API Service

A. Create Base API

File: services/api.ts

import axios from 'axios';
import * as SecureStore from 'expo-secure-store';

// Development: Use your local IP
// Production: Use your API domain
export const BASE_URL = __DEV__ 
  ? 'http://192.168.1.100:3000'  // Replace with your IP
  : 'https://api.lampungdev.org';

export const API_BASE_URL = `${BASE_URL}/api`;

export const api = axios.create({
  baseURL: API_BASE_URL,
  headers: {
    'Content-Type': 'application/json',
  },
  timeout: 10000, // 10 seconds timeout
});

// Request interceptor - add auth token
api.interceptors.request.use(async (config) => {
  try {
    const token = await SecureStore.getItemAsync('auth_token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
  } catch (error) {
    console.error('Error getting token:', error);
  }
  return config;
});

// Response interceptor - handle errors
api.interceptors.response.use(
  (response) => response,
  async (error) => {
    if (error.response?.status === 401) {
      // Token expired - clear auth
      await SecureStore.deleteItemAsync('auth_token');
      await SecureStore.deleteItemAsync('auth_user');
    }
    return Promise.reject(error);
  }
);

B. Create Auth Service

File: services/auth.ts

import { api } from './api';

interface LoginResponse {
  success: boolean;
  data?: {
    token: string;
    user: {
      id: string;
      name: string;
      email: string;
      role: string;
      picture?: string;
    };
  };
  error?: string;
}

export const loginAdmin = async (
  email: string, 
  password: string
): Promise<LoginResponse> => {
  try {
    const response = await api.post('/admin/auth', { email, password });
    return response.data;
  } catch (error: any) {
    return {
      success: false,
      error: error.response?.data?.error || 'Login failed',
    };
  }
};

C. Create Events Service

File: services/events.ts

import { api } from './api';

export interface Event {
  id: string;
  title: string;
  description: string;
  startDate: string;
  endDate: string;
  location: string;
  locationMapUrl?: string;
  imageUrl?: string;
  status: 'DRAFT' | 'UPCOMING' | 'ONGOING' | 'FINISHED';
  _count?: {
    registrations: number;
    attendances: number;
  };
}

export const getAdminEvents = async (): Promise<Event[]> => {
  const response = await api.get('/admin/events');
  return response.data.events || [];
};

export const getEventDetail = async (id: string): Promise<Event> => {
  const response = await api.get(`/admin/events/${id}`);
  return response.data;
};

๐Ÿงช Step 7: Test Setup

Start the development server:

npx expo start

If everything is configured correctly:

  • No errors in terminal
  • QR code displayed
  • App opens on Expo Go

โœ… Checklist

  • Expo project created with tabs template
  • Dependencies installed (axios, zustand, expo-secure-store, expo-camera)
  • NativeWind configured
  • Folder structure created
  • app.json configured
  • Base API service created
  • Auth and Events services created

๐Ÿš€ Next Steps

In Part 4, we'll implement the authentication system with Zustand store, login screen, and auth guard.


โ† Part 2: Installation | Next: Authentication โ†’


This series is created to support the Casual Meetup #15 at LampungDev.

Continue Reading

Previous article

โ† Previous Article

React Native Expo Event Management Part 2: Instalasi di Mac, Windows, Ubuntu

Next Article โ†’

React Native Expo Event Management Part 4: Autentikasi Admin

Next article