DEV Community

Cover image for How to embed a web server in your React-Native app in Swift
Tarek Touati for Zenika

Posted on • Updated on

How to embed a web server in your React-Native app in Swift


React Native makes it easy to build native iOS and Android apps, there's a lot of modules that allow us to use native APIs made by an awesome community. But sometimes it can be frustrating to realize that your needed module does not exist and the only solution you have is to create it on your own. The thing is native modules using React Native should be developed in Swift and Objective-C for iOS and Java/Kotlin for Android.

In this article, we will be creating a module for React Native that interacts with a Swift SDK. The purpose of this module is to turn your iOS device into an HTTP server to serve a static HTML file.

I know what are you thinking about, why would I turn my device into an HTTP server?

So there's a lot of use cases some of them:

  • File Manager/Text editor app and the ability to retrieve/share your files from any other device on your network
  • As a Gateway for IoT
  • As an Ad-hoc server

And maybe the most valuable case is: Just for the fun of it.

Setup new React Native project

You can skip this step if you already have a React Native project.

The first thing to do is to create a new project :

react-native init WebServerApp
cd WebServerApp
Enter fullscreen mode Exit fullscreen mode

Install GCDWebServer pod

GCDWebServer is a library that allows us to create a lightweight HTTP 1.1 server.
To install this library make sure you have CocoaPods installed if it isn't I suggest you Cocoapods Getting Started guide.

let's install our dependency :

Dive into WebServerApp/ios folder.

Open Podfile file in your editor and add :

pod "GCDWebServer", "~> 3.5.3"
Enter fullscreen mode Exit fullscreen mode

Then run the Cocoapod install command

pod install
Enter fullscreen mode Exit fullscreen mode

Bridging Objective-C with Swift :

React Native was made to communicate with Objective-C, that's why we will need to create a bridging header.

React Native Module Schema

In your ios folder, open the code project called WebServerApp.xcworkspace in Xcode.

In Xcode :

  • File -> New -> File or (Cmd + N)
  • Select Swift file
  • Name it WebServerManager

After creating our class, Xcode will suggest you create an Objective-C bridging header (usually this file is called <MY_PROJECT_NAME>-Bridging-header.h):

Press Create Bridging Header button, and you should have a WebServerApp-Bridging-header.h file created

Add to WebServerApp-Bridging-header.h file :

// React Native Bridge
#import "React/RCTBridgeModule.h"

// GCDWebServer headers
#import <GCDWebServer/GCDWebServer.h>
#import <GCDWebServer/GCDWebServerDataResponse.h>
Enter fullscreen mode Exit fullscreen mode

Create WebServerManager Swift class

open WebServerManager.swift file and declare WebServerManager class.

Our class inherited from NSObject so we can expose it to Objective-C

requiresMainQueueSetup method let React Native know if your module needs to be initialized on the main thread

import Foundation

class WebServerManager: NSObject {

     override init(){

    @objc static func requiresMainQueueSetup() -> Bool {
        return true
Enter fullscreen mode Exit fullscreen mode

Exposed WebServerManager methods

Our module will expose only 2 methods which are :

  • startServer
  • stopServer

startServer method :

This method will initialize the server, retrieve the HTML content and return Promise with server URL or threw an error.

  Start `webserver` on the Main Thread
 - Returns:`Promise` to JS side, resolve the server URL and reject thrown errors
  @objc public func startServer(_ resolve: RCTPromiseResolveBlock,
                          rejecter reject: RCTPromiseRejectBlock) -> Void
    if (serverRunning == ServerState.Stopped){
          try self.initWebServer()
          serverRunning = ServerState.Running
          webServer.start(withPort: 8080, bonjourName: "RN Web Server")
          resolve(webServer.serverURL?.absoluteString )
        } catch {

          reject("0", "Server init failed : \(error.localizedDescription)", error)
    } else {
      let errorMessage : String = "Server start failed"
      reject("0", errorMessage, createError(message:errorMessage))
Enter fullscreen mode Exit fullscreen mode

We are using DispatchQueue.main.sync method because it needs to be executed on the Main thread.

Add private Variables and Enumerations

  • ServerState enumerations are differents server state

  • Errors enumerations are error cases

  • webServer variable is an instance GCDWebServer

  • serverRunning variable is the webserver state

  private enum ServerState {
    case Stopped
    case Running
  private enum Errors: Error {
    case fileNotFound
    case fileNotReadable
  private let webServer: GCDWebServer = GCDWebServer()
  private var serverRunning : ServerState =  ServerState.Stopped
Enter fullscreen mode Exit fullscreen mode

Add HTML file :

Create an HTML file with the content you want to serve.

Example :

      <p>This web page is served from your React-Native App</p>
    body {
      background-color: #282c34;
    div {
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      text-align: center;
      min-height: 100vh;
    p {
      color: #fff;
      font-size: xx-large;
      font-weight: 900;
      font-family: sans-serif;
Enter fullscreen mode Exit fullscreen mode

Retrieve the HTML content :

getfileContent method will try to find index.html file and return its content

If the file does not exist or if it can't read the file, it will throw an error

  Read `index.html` file and return its content

 - Throws: `Errors.fileNotReadable`
        if the content of `filePath` is unreadable
        if file in `filePath` is not found
 - Returns: File content

  private func getfileContent() throws -> String{
    if let filePath = Bundle.main.path(forResource: "index", ofType: "html") {
        do {
            let contents = try String(contentsOfFile: filePath)
            return contents
        } catch {
           throw Errors.fileNotReadable
    } else {
      throw Errors.fileNotFound
Enter fullscreen mode Exit fullscreen mode

Generic error method :

createError method takes an error message and returns an NSError

  Creates an NSError with a given message.

 - Parameter message: The error message.

 - Returns: An error including a domain, error code, and error      message.
  private func createError(message: String)-> NSError{
    let error = NSError(domain: "app.domain", code: 0,userInfo: [NSLocalizedDescriptionKey: message])
    return error
Enter fullscreen mode Exit fullscreen mode

Initialize the server :

  Initialization  of the `webserver`
   - Throws: `Errors.fileNotReadable`
              if the content of `filePath` is unreadable
              if  file in `filePath` is not found
  public func initWebServer()throws{
      let content = try getfileContent()
     webServer.addDefaultHandler(forMethod: "GET", request: GCDWebServerRequest.self, processBlock: {request in
        return GCDWebServerDataResponse(html:content)
   } catch Errors.fileNotFound {
      throw createError(message:"File not found")
  } catch Errors.fileNotReadable {
     throw createError(message:"File not readable")
Enter fullscreen mode Exit fullscreen mode

stopServer method :

This method is executed when the server is running. It simply stops the server

  Stop `webserver` and update serverRunning variable to Stopped case
  @objc public func stopServer() -> Void{
    if(serverRunning == ServerState.Running){
      serverRunning = ServerState.Stopped
Enter fullscreen mode Exit fullscreen mode

Expose WebServerManager methods to React Native Bridge

As I said previously, RN was made to talk with Objective-C. So we need to create bridging headers.

  • File -> New -> File or (Cmd + N)
  • Select Objective-C file
  • Name it WebServerManager

And add :

#import "React/RCTBridgeModule.h"

@interface RCT_EXTERN_MODULE(WebServerManager, NSObject)
RCT_EXTERN_METHOD(startServer: (RCTPromiseResolveBlock) resolve
                  rejecter: (RCTPromiseRejectBlock)reject)
Enter fullscreen mode Exit fullscreen mode

We import RCTBridgeModule to use React Native Macros.
Then RCT_EXTERN_MODULE to expose our WebServerManager class to JS side.

Bridging Objective-C and JavaScript

Import WebServerManager module on JS side.

import { NativeModules } from "react-native";

// our native module
const { WebServerManager } = NativeModules;
Enter fullscreen mode Exit fullscreen mode

Complete App.js file

First install react-native-elements dependecy.

yarn add react-native-elements react-native-vector-icons

react-native link react-native-vector-icons
Enter fullscreen mode Exit fullscreen mode
import React, { useState } from "react";
import {
} from "react-native";

import { Icon } from "react-native-elements";
// we import our native module
const { WebServerManager } = NativeModules;

const App: () => React$Node = () => {
  const [endpoint, setEndpint] = useState("");
  const [isServerRunning, setServerState] = useState(false);

  const startServer = () => {
      .then(url => setEndpint(url))
      .then(() => setServerState(true))
      .catch(err => console.error(err));

  const stopServer = () => {

  return (
      <StatusBar barStyle="light-content" />
      <SafeAreaView style={styles.safeView}>
        <View style={styles.infoBlock}>
          <Text style={styles.text}>
            Press button to turn {isServerRunning ? "Off" : "On"} server
        <View style={styles.container}>
              color={isServerRunning ? "#01b907" : "#f44336"}
              onPress={() => (isServerRunning ? stopServer() : startServer())}
        {isServerRunning ? (
          <View style={styles.container}>
            <Text style={{ ...styles.text, ...styles.urlEndpoint }}>
              Server is available at this Url : {endpoint}
        ) : (
          <View style={styles.container} />

const styles = StyleSheet.create({
  safeView: {
    backgroundColor: "#282c34",
    height: "100%"
  urlEndpoint: {
    paddingTop: 20
  text: {
    color: "#FFF",
    fontWeight: "900",
    fontSize: 20,
    textAlign: "center"
  infoBlock: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center"
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center"

export default App;
Enter fullscreen mode Exit fullscreen mode

Then run the app in the simulator.

react-native run-ios
Enter fullscreen mode Exit fullscreen mode

Your app should look like this :

iPhone screen

Press button and enter given URL in your browser and you should see this :

Browser screen

You can find the complete project on Github

Top comments (1)

danebrown profile image
Dane Brown

Really helpful and well written. Very good job!