DEV Community

Cover image for Inside React Native’s New Bridge: Codegen, TurboModules, and the Future
Amit Kumar
Amit Kumar

Posted on • Edited on

3 1 2 2 1

Inside React Native’s New Bridge: Codegen, TurboModules, and the Future

React Native is getting faster and cleaner. With the New Architecture, you don’t need to write tons of bridging code anymore. Thanks to Codegen and TurboModules, we now have:

✅ Automatic native binding
✅ Type-safe communication
✅ Better performance


In this guide, we’ll break down the concepts and build a real-world example module—step-by-step for both Android and iOS—that demonstrates:

✅ Sending a message from JS → Native
✅ Getting a message from Native → JS (via Promise)
✅ Using callbacks to return values
✅ Emitting real-time events from Native → JS

Let’s dive in. 👇


📘 What’s Codegen?

Codegen is the code generation system that automatically creates native interface files from TypeScript specifications.


✅ What Codegen Does:

  • Reads TypeScript spec files (like specs/NativeMessageModule.ts)
  • Generates native interface files for both Android and iOS
  • Creates type-safe bindings between JavaScript and native code
  • Handles the boilerplate code automatically

⚙️ What’s TurboModules?

Turbo Modules is the runtime architecture that provides fast, type-safe communication between JavaScript and native code.


✅ What Turbo Modules Does:

  • Provides the runtime infrastructure for native-JS communication
  • Implements the TurboModule interface
  • Offers better performance than the legacy bridge
  • Enables synchronous calls and better memory management

Aspect Old Bridge New (TurboModules + Codegen)
Manual bridging ✅ Required ❌ Auto-generated via Codegen
Performance 🐢 Slower 🚀 Much faster
Type safety ❌ Risk of mismatch ✅ Enforced via TypeScript
Native ↔ JS Async only Async + Sync support

Why Both Are Needed

  • Without Codegen: You'd have to manually write all the interface files
  • Without Turbo Modules: You'd still use the slow legacy bridge system
  • With Both: You get automatic code generation + fast runtime performance

Think of it this way:

  • Codegen = The factory that builds the blueprint
  • Turbo Modules = The engine that makes everything run fast

⚙️ My Current Configuration

Here's the exact setup I used for testing these solutions:

{
  "dependencies": {
    "react": "19.0.0",
    "react-native": "0.79.3"
  }
}

Enter fullscreen mode Exit fullscreen mode

📁 Project Structure Overview

Here's how to structure your project to support TurboModules:


yourapp/
├── android/
   └── app/
       └── src/
           └── main/
               └── java/
                   └── com/
                       └── sampleapp/
                           ├── NativeMessageModule.java
                           ├── NativeMessagePackage.java
                           └── MainApplication.java
├── ios/
   ├── NativeMessageModule.h
   └── NativeMessageModule.mm
├── specs/
   └── NativeMessageModule.ts
├── App.js
├── package.json
└── ...


Enter fullscreen mode Exit fullscreen mode

✅ Step-by-Step Android Example


1. 📄 specs/NativeMessageModule.ts

This is your TypeScript interface spec — the source of truth for Codegen.

import {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport';
import {TurboModuleRegistry} from 'react-native';
import { EventEmitter } from 'react-native/Libraries/Types/CodegenTypes';

export interface Spec extends TurboModule {
  sendMessage(message: string): void;
  getMessage(): Promise<string>;
  sendWithCallback(callback: (response: string) => void): void;

  addListener(eventName: string): void;
  removeListeners(count: number): void;
  startSendingEvents(): void;
}

export default TurboModuleRegistry.getEnforcing<Spec>('NativeMessageModule');




Enter fullscreen mode Exit fullscreen mode

All Native Turbo Module spec files must have the prefix Native, otherwise Codegen will ignore them.

The string 'NativeMessageModule' must exactly match public static final String NAME = "NativeMessageModule"; in your Java module. This consistency is critical for Codegen and TurboModules to connect properly.


2. 🧠 codegenConfig in package.json

Tells Codegen how to generate files.

"codegenConfig": {
  "name": "NativeMessageModule",
  "type": "modules",
  "jsSrcsDir": "specs",
  "android": {
    "javaPackageName": "com.turbomodules" #Replace this with your packagename
  }
}


Enter fullscreen mode Exit fullscreen mode

android

Codegen is executed through the generateCodegenArtifactsFromSchema Gradle task:


cd android
./gradlew generateCodegenArtifactsFromSchema

BUILD SUCCESSFUL in 837ms
14 actionable tasks: 3 executed, 11 up-to-date

Enter fullscreen mode Exit fullscreen mode

This is automatically run when you build your Android application.


iOS

You need to install the pods to make sure that codegen runs to generate the new files:

cd ios 
pod install

Enter fullscreen mode Exit fullscreen mode

3. 🧱 NativeMessageModule.java

✅ File: NativeMessageModule.java

📍 Path: android/app/src/main/java/com/sampleapp/NativeMessageModule.java

This is your real native logic class.


package com.turbomodules; #Replace this with your packagename

package com.turbomodules;

import android.os.Handler;
import android.os.Looper;

import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.modules.core.DeviceEventManagerModule;

@ReactModule(name = NativeMessageModule.NAME)
public class NativeMessageModule extends NativeMessageModuleSpec {
    public static final String NAME = "NativeMessageModule";

    public NativeMessageModule(ReactApplicationContext reactContext) {
        super(reactContext);
    }

    // 1. Send message (no return)
    public void sendMessage(String message) {
        System.out.println("Received from JS: " + message);
    }

    // 2. Return message using Promise
    public void getMessage(Promise promise) {
        try {
            String message = "Hello from Android";
            promise.resolve(message);
        } catch (Exception e) {
            promise.reject("GET_MESSAGE_ERROR", e);
        }
    }

    // 3. Return message using Callback
    public void sendWithCallback(Callback callback) {
        String response = "Callback response from Android";
        callback.invoke(response);
    }

    // 4. Send event to JS
    @Override
    public void addListener(String eventName) {
        // Required for Turbo Module event emitters
        // This is called automatically when JS adds a listener
    }

    // 5. Remove event listeners
    @Override
    public void removeListeners(double count) {
        // Required for Turbo Module event emitters
        // This is called automatically when JS removes listeners
    }

    // 6. Emit an event to JS
    @ReactMethod
    public void startSendingEvents() {
        Handler handler = new Handler(Looper.getMainLooper());
        for (int i = 1; i <= 5; i++) {
            int delay = i * 1000;
            handler.postDelayed(() -> {
                WritableMap map = Arguments.createMap();
                map.putString("message", "Event at " + System.currentTimeMillis());
                getReactApplicationContext()
                    .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                    .emit("onTimerTick", map);
            }, delay);
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

4. 📦 NativeMessagePackage.java

✅ File: NativeMessagePackage.java
📍 Path:
android/app/src/main/java/com/sampleapp/NativeMessagePackage.java

Registers your module in the app.


package com.turbomodules; #Replace this with your packagename

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.*;
import com.facebook.react.uimanager.ViewManager;
import java.util.List;

public class NativeMessagePackage implements ReactPackage {
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext context) {
        return List.of(new NativeMessageModule(context));
    }

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext context) {
        return List.of();
    }
}


Enter fullscreen mode Exit fullscreen mode

💡 Why needed: This is how you expose your native module to React Native.


5. 🏠 MainApplication.java

✅ File: MainApplication.java
📍 Path:
android/app/src/main/java/com/sampleapp/MainApplication.java


import com.turbomodules.NativeMessagePackage #Replace this with your packagename

add(NativeMessagePackage())

Enter fullscreen mode Exit fullscreen mode

6. ✅ Step-by-Step iOS Example

With the Android module complete, let’s build the iOS side of our NativeMessageModule. We'll use Objective-C++ with TurboModules and Codegen to enable seamless native-JS communication.


📁 Step 1: Open the Xcode Workspace

Navigate to the ios folder and open your project workspace in Xcode:

cd ios
open TurboModuleExample.xcworkspace


Enter fullscreen mode Exit fullscreen mode

📦 Step 2: Create a New Group

In Xcode:

  1. Right-click your app folder (e.g., TurboModuleExample)
  2. Select New Group
  3. Name it: NativeMessageModule

✅ This keeps native modules organized by feature.



🧱 Step 3: Add a New Cocoa Touch Class

Now add the module class file:

  1. Right-click NativeMessageModule group
  2. Select New File from Templetes...


Step 4: Choose Cocoa Touch Class


⚙️ Step 5: Name the Cocoa Touch Class NativeMessageModule with the Objective-C language.


Note:- Rename NativeMessageModule.mNativeMessageModule.mmmaking it an Objective-C++ file.


Final file Structure

Here's what your folder structure should look like:


✅ Configure the Bridging Header

To ensure proper bridging between Swift and Objective-C:

  1. Go to Build Settings in your Xcode project.
  2. Search for Objective-C Bridging Header.
  3. Set the path relative to your project like this:


🧬 Step 6: Implement the Native Module

Update the content of both NativeMessageModule.h and NativeMessageModule.mm as follows:


📄 NativeMessageModule.h


//
//  NativeMessageModule.h
//  newArchitectureBridge
//
//  Created by Amit Kumar on 21/06/25.
//

#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>

NS_ASSUME_NONNULL_BEGIN

@interface NativeMessageModule : RCTEventEmitter <RCTBridgeModule>

@end

NS_ASSUME_NONNULL_END



Enter fullscreen mode Exit fullscreen mode

⚙️ NativeMessageModule.mm
Now update NativeMessageModule.mm

//
//  NativeMessageModule.mm
//  newArchitectureBridge
//
//  Created by Amit Kumar on 21/06/25.
//

#import "NativeMessageModule.h"

@implementation NativeMessageModule {
  BOOL hasListeners;
}

RCT_EXPORT_MODULE();

+ (BOOL)requiresMainQueueSetup {
  return YES;
}

- (NSArray<NSString *> *)supportedEvents {
  return @[@"onTimerTick"];
}

// Called when the first JS listener is added
- (void)startObserving {
  hasListeners = YES;
}

// Called when the last JS listener is removed
- (void)stopObserving {
  hasListeners = NO;
}

RCT_EXPORT_METHOD(sendMessage:(NSString *)message) {
  NSLog(@"[iOS] Received from JS: %@", message);
}

RCT_EXPORT_METHOD(getMessage:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
  NSString *response = @"Hello from iOS";
  NSLog(@"[iOS] Returning to JS (Promise): %@", response);
  resolve(response);
}

RCT_EXPORT_METHOD(sendWithCallback:(RCTResponseSenderBlock)callback) {
  NSString *response = @"Hello from iOS";
  NSLog(@"[iOS] Returning to JS (Callback): %@", response);
  callback(@[response]);
}

RCT_EXPORT_METHOD(startSendingEvents) {
  if (!hasListeners) {
    NSLog(@"[iOS] No listeners, not sending events");
    return;
  }

  for (int i = 1; i <= 8; i++) {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(i * NSEC_PER_SEC)),
                   dispatch_get_main_queue(), ^{
      if (!hasListeners) return;
      NSString *msg = [NSString stringWithFormat:@"Event at %f", [[NSDate date] timeIntervalSince1970]];
      [self sendEventWithName:@"onTimerTick" body:@{@"message": msg}];
    });
  }
}

@end


Enter fullscreen mode Exit fullscreen mode

7. 🧪 JS Test: App.js

import React, {useState, useEffect} from 'react';
import {
  View,
  Button,
  Text,
  StyleSheet,
  NativeEventEmitter,
  NativeModules,
} from 'react-native';
import NativeMessageModule from './specs/NativeMessageModule';

const App = () => {
  const [receivedMessage, setReceivedMessage] = useState('');
  const [lastSentMessage, setLastSentMessage] = useState('');
  const [callbackMsg, setCallbackMsg] = useState('');
  const [eventMsg, setEventMsg] = useState('');

  useEffect(() => {
    const eventEmitter = new NativeEventEmitter(
      NativeMessageModule,
    );
    const subscription = eventEmitter.addListener('onTimerTick', event => {
      console.log('Event received:', event.message);
      setEventMsg(event.message);
    });
    return () => subscription?.remove();
  }, []);

  const startListening = () => {
    NativeMessageModule.startSendingEvents();
  };

  const sendMessageToNative = () => {
    const message = 'Hello from React Native';
    NativeMessageModule.sendMessage(message);
    setLastSentMessage(message);
  };

  const getMessageFromNative = () => {
    try {
      const message = NativeMessageModule.getMessage();
      setReceivedMessage(message);
    } catch (error) {
      console.error(error);
    }
  };

  const useCallbackMessage = () => {
    NativeMessageModule.sendWithCallback(msg => {
      setCallbackMsg(msg);
    });
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Bridging Example</Text>

      <View style={styles.buttonContainer}>
        <Button title="Send Message to Native" onPress={sendMessageToNative} />
        <Text style={styles.message}>Last sent: {lastSentMessage}</Text>
      </View>

      <View style={styles.buttonContainer}>
        <Button
          title="Get Message from Native"
          onPress={getMessageFromNative}
        />
        <Text style={styles.message}>Received: {receivedMessage}</Text>
      </View>

      <View>
        <Button title="Use Callback" onPress={useCallbackMessage} />
        <Text>Received: {callbackMsg}</Text>
      </View>

      <View>
        <Button title="Trigger Native Event" onPress={startListening} />
        <Text>Received: {eventMsg}</Text>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {flex: 1, justifyContent: 'center', padding: 16},
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 32,
    textAlign: 'center',
  },
  buttonContainer: {marginBottom: 24},
  message: {marginTop: 8, fontSize: 14, color: '#666'},
});

export default App;



Enter fullscreen mode Exit fullscreen mode

Final Output


🎯 Conclusion

With TurboModules and Codegen, React Native’s new architecture simplifies bridging and boosts performance:

  • No more boilerplate for native interfaces
  • Faster & safer communication
  • Clear folder structure for maintainability

Whether you're migrating from the legacy bridge or starting fresh, this setup gives you everything you need to build efficient, modern native modules.

Warp.dev image

The best coding agent. Backed by benchmarks.

Warp outperforms every other coding agent on the market, and gives you full control over which model you use. Get started now for free, or upgrade and unlock 2.5x AI credits on Warp's paid plans.

Download Warp

Top comments (0)

AWS Q Developer image

Build your favorite retro game with Amazon Q Developer CLI in the Challenge & win a T-shirt!

Feeling nostalgic? Build Games Challenge is your chance to recreate your favorite retro arcade style game using Amazon Q Developer’s agentic coding experience in the command line interface, Q Developer CLI.

Participate Now

👋 Kindness is contagious

Dive into this thoughtful piece, beloved in the supportive DEV Community. Coders of every background are invited to share and elevate our collective know-how.

A sincere "thank you" can brighten someone's day—leave your appreciation below!

On DEV, sharing knowledge smooths our journey and tightens our community bonds. Enjoyed this? A quick thank you to the author is hugely appreciated.

Okay