Apple Push Notifications with React Native and Node.js

How to implement APNs for React Native iOS apps

This article walks through a Javascript implementation of Apple Push Notification (APN) support for React Native projects. We will be covering APIs on the React Native side to get your app supporting APNs, as well as Node JS APIs for sending APNs server-side. The latter solution is just as important to implement so your backend server has full control over sending Remote Notifications, that often tie in with server-side processes rather than on-device state.

If you are using Expo, the article assumes that you have an ejected Expo project, and does not rely on Expo’s own Push Notification service.

To complete this setup, you will need an active Apple Developer licence or admin access to the Developer Portal, in order to configure provisioning Identifiers and Keys.

There are indeed some setup steps we must undertake outside the realm of Javascript, that consist on the Apple Developer portal and within Xcode:

  • Configuring Push Notification support for the app within your App’s Identifier.
  • Generating a Key from the Apple Developer portal to use as a means of authentication when we’re making notification requests to Apple’s servers.
  • Making sure the required delegate methods are included in your AppDelegate.m file within Xcode to expose the Push Notification API — most of which come by default when detaching from an Expo project, or spinning up a new React Native project. Nonetheless, this article will cover these methods too.

Before exploring the Javascript packages at our disposal to get APNs integrated with ease, the article firstly gets the non-Javascript requirements documented, running through the app provisioning requirements to get Push Notifications supported.

App Provisioning

A good place to start with provisioning options and requirements is within Xcode, as over the years, the software has become more intelligent at automatically managing the app provisioning process for developers to further streamline the process.

Your Xcode project should exist within the ios/ folder at the top level of your React Native project. If you are coming from a bare-bones Expo project, this folder is generated upon ejecting an Expo managed app. It may already exist if you opted to have them generated when your project was initialised.

Open the .xcworkspace file within your project’s ios/ folder to fire up the Xcode project — this file will be named <project_name>.xcworkspace.

Opening the workspace file ensures your workspace state is persisted between visits, and should be standard practice.

With your app selected in the Project Navigator (the far left panel — press CMD+1 if it is not showing), visit the Signing and Capabilities tab in the main view. Directly below this tab you should see an + Capability button. Click this button and add the Push Notifications capability.

Per Apple’s documentation on Capabilities, adding a Capability edits the Entitlements and Information Property List files, adds related frameworks, and configures your signing assets. So not only is the Capability in question supported on a framework level, Xcode also attempts to provision the app to support the feature too.

If you have Automatically manage signing enabled (still in Signing and Capabilities), Xcode will take care of your provisioning for you. If you have however already manually provisioned your app in the Developer portal, Push Notifications will need to be enabled in your App ID configuration.

Let’s briefly cover this next.

To ensure Push Notifications are enabled, make sure the feature is checked within your App Identifier configuration:

Enabling Push Notifications within App Identifier

Clicking “Edit” opens up a modal, allowing you to generate and download SSL certificates to communicate with the APN gateway for development and production environments. Even though these certificates are not required in this APN setup (we’ll be using an APN Key instead — more on this next), you may wish to generate the certificates to familiarise yourself with the process.

Apple’s Push Notification service provides endpoints for a sandbox environment and production environment. These SSL certificates coincide with these two environments, and are not tied to your React Native or server-side environment.

E.g. you could test sandbox notifications on your production servers before moving to the real service.

As briefly mentioned above, using these generated certificates is not required to complete the setup in this article — we will be using an APN Key instead — we’ll generate one of those next. However, for the sake of completeness, it is important that the reader understands that these certificates exist and can also be used as a means of authentication when requesting notifications to be sent — as well as to not be confused between the two.

To read more about how these certificates are used with the Apple Notification Service, check out Apple’s official document: Establishing a Certificate-Based Connection to APNs.

Registering an APN Key

Generating a Key is something Xcode does not do (at the time of writing). For this, we are required to jump into the Developer Portal and manually generate this key file.

Like Certificates, a Key acts as an authentication mechanism to prove to the APN service who is requesting a notification to be sent. Keys have become a more standard means of authenticating with the APN service, that will become evident further down as we use them with our server-side APIs.

Inside the Developer Portal in the Certificates, Identifiers & Profiles section, there is another sub-section named Keys. Go here and press the “+” icon to configure a new key.

Configuring a new Key for the APN service

Once configured, go ahead and download the key, keeping in mind that once downloaded, you cannot re-download it. This key will be used later on when we configure an APN provider on the server side.

If you lose your key, revoke it within the Developer Portal and generate another one. Keys cannot be recovered.

With the necessary provisioning complete, we can turn our attention to the React Native side of the project.

Implementing APNs within React Native

With provisioning complete, we’ve now arrived at the React Native side. React Native is responsible for a couple of things for setting up APNs:

  • Pulling up the Request Permission dialogue at a particular point in the app to give the user an opportunity to accept or reject Push Notifications. After which the user will have to delve into their Settings to enable notifications, if they initially reject them.
  • Registering a device token, and providing it to us in plain text in Javascript so we can use it when sending notifications.

A device token is a unique identifier for a particular device that APNs are enabled for, and will only be presented if Push Notifications are indeed enabled for the app.

To integrate these features we’ll be utilising two packages, that streamline the process into easy-to-use APIs:

  • react-native-push-notification: A simple wrapper for React Native that provides both Local and Remote Notifications support, in the form of some simple APIs we can invoke to call the Push Notification permission, and register a device token. This package also provides support for Android notifications in the event you want one unified package to support both iOS and Android platforms.
  • @react-native-community/push-notification-ios: The go-to package for React Native APN support, super-seeding the original PushNotificationIOS component the React Native team provided. react-native-push-notification actually depends on this package, that is called under the hood of its simplified interface.

These are both open source community packages that have enhanced and simplified the integration process of APNs — using both of them gives us a relatively simple means of supporting them in our React Native component tree.

Install the packages and link them to your native app in the process, along with updating your Pods:

Cocoapods is a dependency manager for Swift and Objective-C Cocoa projects, the packages of which often acting as a counterpart of React Native NPM packages that offer iOS feature support.

// install packages
yarn add react-native-push-notification
yarn add @react-native-community/push-notification-ios
// link
react-native link react-native-push-notification
// update Pods
cd ios && pod install

This installation process should be pain free, relying on pod to update your native dependencies. Even though the latest version of React Native supports automatic linking, I would still include the link command if you are unsure whether auto-linking is supported in your project.

We can invoke the Request Permission dialogue by calling react-native-push-notification's configure() method, that will bring up a prompt like the following:

Request Push Notifications Permission Dialogue Box

This will be the point at which the user allows or disallows the use of notifications. If “OK” is tapped, the device token will be registered and returned from configure()’s onRegister event, allowing us to handle it as we please.

If the user has already accepted Push Notifications, configure() will trigger its onRegister event immediately, skipping the Request Permission dialogue.

Concretely, configure() will return the device token on every subsequent call, after the user has enabled Push Notifications.

The Usage section of the package outlines configure()’s options, ranging from the permission object itself to event handlers. The following Gist is a minimal implementation that handles the registering of a device token, wrapping it in a configure() function:

To save duplicating the documentation, check out the Usage section of react-native-push-notification to check out all the options available.

The above relies on default value of true for requestPermissions, that will automatically invoke the Request Permission prompt. Upon the user accepting, onRegister is triggered, giving us the opportunity to handle the token as we please — that may include:

  • Sending it off to your server, perhaps at an apn/save route that will persist the device token in your database.
  • Persisting the device token locally on-device using AsyncStorage, that could prevent configure() to be called again on subsequent visits to the app in the event the device token already exists.

Perhaps the most elegant way of implementing configure() is by putting it into a React Context, making it available for any component to invoke. If you wanted the dialogue to pop up once a user signs into your app for example, you could call configure() within a <Dashboard /> component.

Having all logic within a context component also keeps APN logic in one file.

To learn more about Context, check out another of my recently published articles that uses Context to make scroll-aware header transitions work: React Native & TypeScript: Scroll Aware Header Transitions.

The following Gist expands on the previous one, now defining an APNManager component housing an APNContext, in addition to a useApn() context hook.

Notice also that we are wrapping the component with Redux, that is providing an authentication token to the component. The component can now send a newly registered device token to the app server along with the user’s auth token, in order to persist the device token with that particular user account:

Now, if we wrap <APNManager /> around the entire app, all components will have the ability to call configure(), such as the aforementioned <Dashboard /> component that could make use of the useApn() context hook:

// dashboard calling configure()...
import { useApn } from './APNManager'
export const Dashboard = (props: any) => { const apn = useApn(); useEffect(() => {
apn.configure();
}, []);
...
}

useEffect() has been used in the above example, with an empty array for conditional updates— this means apn.configure() will only be called once after the component’s initial render.

The react-native-push-notification package does more than what this article has outlined, such as handle Local Notifications, as well as Scheduled Notifications. Check out the full documentation to see everything the package supports.

There is one more piece to the React Native puzzle for APNs, and that is implementing their corresponding Objective-C APIs within Xcode — the packages we have been utilising will use these APIs under the hood.

If we refer to the @react-native-community/push-notification-ios package’s documentation in the Updating AppDelegate.m section, all that is needed is to copy the import statements and delegate methods into your corresponding AppDelegate.m file.

Some of these methods may already be defined within your AppDelegate.m file — simply do not include them if this is the case, only copying the methods absent from your project.

For the last section of this piece, we will explore actually sending notifications server-side in a Node.js environment, using the APN Key generated earlier along with the registered device tokens from the device.

Sending Notifications Server-Side with Node.js

Realistically, your server-side logic will be in charge of firing notifications to your app users, and not the on-device logic itself. The purpose of notifications are to get the user’s attention while they are outside of the app, or off their phone completely; there will be edge cases where notifications will be scheduled in-app, but in most scenarios your server will be driving remote notifications — and Node.js is a great platform for managing such a service.

Like the React Native side of this implementation, there is a well-designed set of APIs available in an open source package, that is simply named apn, also known as Node-APN.

To support APNs on your server, whether in an Express server or standalone process, simply install apn with NPM or yarn:

yarn add apn

Implementing this package is a two stage process:

  • Defining an apnProvider object, that initialises the APN service by connecting to Apple’s Push Notification provider API. This is where the APN Key generated earlier will be provided, along with your Team ID (found on your Developer Portal Membership page), and Key ID (provided with the APN Key was generated, and available to grab from your Keys section).
  • Configuring a notification object and passing it through your apnProvider’s send() method. This will essentially pass the notification to Apple’s gateway and distribute the notification to the supplied device tokens, also providing a callback function for handling success and failures or a per-device-token basis.

The following Gist initialises an APN Provider, taking into consideration the environment, and credentials needed to connect to the service:

Note that the ./certs folder and confidential.js file should be ignored from source control; make sure any sensitive files and directories are included in .gitignore.

Your APN Provider only needs to be initialised once. If you are triggering notifications through an Express server, make sure it is initialised outside of a particular route.

The above implementation will successfully connect to the APN gateway, but for all the available options, refer to the provider documentation page.

With your APN Provider initialised, you are ready to send notifications. We simply need to provide a notification using apn.Notification(), and pass that into the provider’s send() method.

Here is an example notification object:

let notification = new apn.Notification({
alert: {
title: 'Hello World',
body: 'Hello world body'
},
topic: 'com.org.AppName',
payload: {
"custom": "value",
},
pushType: 'background'
});

The above example is indeed minimal, but demonstrates the requirements to get a notification working — a title and body, along with a topic and pushType. Some key points are to be made here:

  • In the above example we’ve defined alert.title and alert.body, but there are actually just title and body fields available, too. However, alert.title and alert.body take precedence over title and body.
  • topic must match the App Bundle ID, and is required.
  • pushType is required on iOS 13 or later, and should be included in all your requests. Either background or alert should be provided, although background is more user friendly in that it does not disrupt the user’s current activity when the notification is delivered.

Check out the full notification settings here, taking note of the options that will suit your use case.

From here, the notification object can be passed into apnProvider’s send() method to dispatch the notification, providing it with an array of device tokens to send the notification to:

// dispatching a notificationconst deviceTokens = [
'<device_token_string>'
];
apnProvider.send(notification, deviceTokens).then(response => {

// successful device tokens
console.log(response.sent);
// failed device tokens
console.log(response.failed);
});

The API separates which devices successfully received the notification, and which ones did not. If you are experiencing consistent failures for a particular device token, you could blacklist it or simply remove it from an associated user account — and let configure() on the React Native side provide an updated token.

The following last Gist triggers the send() method inside an Express route, getting the device tokens within the request body. It then logs out both the successful device tokens and failed device tokens.

In Summary

APNs have come a long way since their inception, where Apple originally showcased a PHP solution for handling notification requests to the Apple notification gateway server-side, and plain Objective C methods server side — with no Cocoapods or React Native linking.

The modern Javascript solution discussed here demonstrates with the power of open source tools that have provided a far superior API for React Native and Node.js environments, fuelled by the vibrant Javascript community.

For issues and queries, please post in the comments.

Programmer and Author. Director @ JKRBInvestments.com. Creator of LearnChineseGrammar.com for iOS and Android.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store