Expo HTTP Server
expo-http-server is an Expo module that provides a simple HTTP server implementation exclusively for iOS and Android React Native applications. It is currently at version 0.1.13 and sees active development with recent updates for Expo 53 compatibility and feature enhancements like custom headers. The module leverages native libraries, Criollo for iOS and AndroidServer for Android, offering local network communication capabilities directly from the mobile device. A key differentiator is its focus on embedding a server within a mobile app, enabling scenarios like local API mocking, inter-app communication, or serving local assets without a remote backend. It notably does not support web environments, which is a crucial limitation to understand.
Common errors
-
TypeError: server.start is not a function
cause The module was either not imported correctly (e.g., using CommonJS require in a modern Expo setup) or the application is running in an unsupported environment like a web browser.fixEnsure you are using `import * as server from 'expo-http-server';` and verify that the application is running on an iOS or Android device/emulator, not in a web browser. -
Server not starting on iOS when app is backgrounded / Server stops responding after a short time in background (iOS)
cause iOS operating system limitations impose strict time limits on background tasks, causing the server to pause.fixThis is expected behavior on iOS. The server automatically pauses and resumes. For critical background operations, consider alternative inter-process communication or design patterns that do not rely on a continuously running HTTP server in the background on iOS. -
TypeError: Cannot read property 'setup' of undefined in browser environment
cause The `expo-http-server` module does not have a web implementation and its native modules are undefined when running in a browser.fixThis module is exclusively for iOS and Android. If your project targets web, use conditional imports or a web-specific HTTP server solution. -
My route handler isn't being called / Route path doesn't seem to match
cause Possible issues with route definition, conflicting routes, or changes in how routes are matched internally (e.g., v0.1.10 change).fixDouble-check the path, method, and callback definitions for `server.route()`. Ensure there are no overlapping routes. Refer to the v0.1.10 breaking change regarding UUID-based matching.
Warnings
- breaking The internal mechanism for matching callbacks changed from relying solely on path and method to using a UUID. This might affect advanced use cases or debugging if prior versions were implicitly relying on specific routing implementation details.
- breaking For iOS, the body of incoming requests is now consistently sent as a JSON string. This changes the expected format for request body parsing on the server side.
- gotcha The server does not support web environments. It is strictly for iOS and Android React Native applications.
- gotcha On iOS, when the app is backgrounded, the HTTP server will inevitably pause after approximately 25 seconds due to operating system limitations, even with background tasks. The server will resume when the app returns to the foreground.
- gotcha Running an HTTP server directly on a mobile device introduces potential security considerations. Ensure proper access control, input validation, and avoid exposing sensitive data or functionality without strong authentication.
Install
-
npm install expo-http-server -
yarn add expo-http-server -
pnpm add expo-http-server
Imports
- * as server
const server = require('expo-http-server');import * as server from 'expo-http-server';
- StatusEvent
import type { StatusEvent } from 'expo-http-server'; - Request
import type { Request } from 'expo-http-server';
Quickstart
import * as server from 'expo-http-server';
import { useEffect, useState } from 'react';
import { Text, View } from 'react-native';
export default function App() {
const [lastCalled, setLastCalled] = useState<number | undefined>();
const html = `
<!DOCTYPE html>
<html>
<body style="background-color:powderblue;">
<h1>expo-http-server</h1>
<p>You can load HTML!</p>
</body>
</html>`;
const obj = { app: 'expo-http-server', desc: 'You can load JSON!' };
useEffect(() => {
server.setup(9666, (event: server.StatusEvent) => {
if (event.status === 'ERROR') {
console.error('Server error:', event.message);
} else {
console.log('Server status:', event.status);
}
});
server.route('/', 'GET', async (request) => {
console.log('Request to / (GET)', request);
setLastCalled(Date.now());
return {
statusCode: 200,
headers: {
'Custom-Header': 'Bazinga',
},
contentType: 'application/json',
body: JSON.stringify(obj),
};
});
server.route('/html', 'GET', async (request) => {
console.log('Request to /html (GET)', request);
setLastCalled(Date.now());
return {
statusCode: 200,
statusDescription: 'OK - CUSTOM STATUS',
contentType: 'text/html',
body: html,
};
});
server.start();
return () => {
server.stop();
};
}, []);
return (
<View
style={{
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
}}
>
<Text>
{lastCalled === undefined
? 'Request webserver to change text'
: 'Called at ' + new Date(lastCalled).toLocaleString()}
</Text>
</View>
);
}