Connecting an iOS app to a Redis server can be highly beneficial, as it bridges the gap between mobile development and scalable backend solutions. Redis, known for its speed and efficiency, enhances performance by enabling fast caching, real-time data synchronization, and low-latency operations—critical for features like offline data storage, live notifications, and session management.
In this post, we will build a Dockerized Node.js backend connected to a Dockerized Redis server. The client will be a simple iOS app that fetches a price list and updates the prices as well.
Redis server
The server is a sample Node.js application that will act as a backend facade. Let’s create a blank Node.js project:
npm init -y

To implement the server, we will need dotenv
for loading environment variables from a .env
file, express
for setting up REST API endpoints, and ioredis
for connecting to the Redis server.
npm install dotenv express ioredis

Node.Js server is following:
require('dotenv').config(); // Fetch environment variables
const express = require('express');
const Redis = require('ioredis');
const app = express();
// Fetch environment variables
const PORT = process.env.PORT || 3000;
const REDIS_HOST = process.env.REDIS_HOST || 'localhost';
const REDIS_PORT = process.env.REDIS_PORT || 6379;
// Connect with Redis by using environnment variables
const redis = new Redis({ host: REDIS_HOST, port: REDIS_PORT });
app.use(express.json());
// 📌 Init some prices
async function initializeSamplePrices() {
const initialPrices = {
"iphone": "999",
"macbook": "1999",
"ipad": "799",
"airpods": "199"
};
for (const [product, price] of Object.entries(initialPrices)) {
await redis.set(`price:${product}`, price);
}
console.log("✅ Prices initialized in Redis.");
}
// Initialize sample prices
initializeSamplePrices();
// 📌 Endpoint for getting a product price
app.get('/price/:product', async (req, res) => {
const price = await redis.get(`price:${req.params.product}`);
if (price) {
res.json({ product: req.params.product, price: price });
} else {
res.status(404).json({ error: "Product not found" });
}
});
app.get('/prices', async (req, res) => {
try {
const keys = await redis.keys("price:*");
// Fetch all prices
const prices = await Promise.all(keys.map(async (key) => {
const product = key.replace("price:", "");
const price = await redis.get(key);
return { product: product, price: price };
}));
res.json(prices);
} catch (error) {
console.error("Error on getting prices:", error);
res.status(500).json({ error: "Error on getting prices" });
}
});
// 📌 Endpoint for adding or updating a product price
app.post('/price', async (req, res) => {
const { product, price } = req.body;
if (!product || !price) {
return res.status(400).json({ error: "Misssing data" });
}
await redis.set(`price:${product}`, price);
res.json({ mensaje: "Price stored", product, price });
});
// 📌 Server listening on specied port
app.listen(PORT, () => console.log(`🚀 Server running on http://localhost:${PORT}`));
This Node.js application sets up an Express server that interacts with a Redis database to store and retrieve product prices. It begins by loading environment variables using dotenv
and establishes a connection to Redis with configurable host and port settings. When the server starts, it initializes Redis with some sample product prices (iPhone, MacBook, iPad, and AirPods). The Express app is configured to parse JSON requests, enabling it to handle API interactions effectively.
The application provides multiple API endpoints: one for retrieving the price of a specific product (GET /price/:product
), another for fetching all stored product prices (GET /prices
), and a third for adding or updating a product price (POST /price
). When a client requests a price, the server queries Redis, returning the value if found or a 404 error otherwise. The /prices
endpoint retrieves all stored product prices by listing Redis keys and mapping them to their values. The /price
POST endpoint allows clients to add or update prices by sending a JSON payload. Finally, the server listens on the configured port and logs a message indicating it is running.
Sensitive intormateion is stored in .env file:
# Redis Configuration
REDIS_HOST=redis
REDIS_PORT=6379
# Server Configuration
PORT=3000
Important: Please add .env
to .gitignore
to avoid compromising critical information in production environments. The next step is to define the Dockerfile for the Node.js server to ensure it is properly dockerized.
FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
The Dockerfile sets up a containerized Node.js application. It begins with the official Node.js 18 image, sets the working directory to /app
, and copies the package.json
and package-lock.json
files into the container. Then, it installs the dependencies using npm install
. Afterward, it copies the rest of the application files into the container. The container exposes port 3000 and specifies node server.js
as the command to run when the container starts, which typically launches the application server.
To strictly copy only the necessary files into the container, create the following .dockerignore
file:
node_modules npm-debug.log .env .git
The backend solution consists of two containerized servers. On one side, we have a Node.js backend facade that interacts with the iOS app. On the other side, the backend relies on cache storage in a Redis server.
services:
redis:
image: redis:latest
container_name: redis-server
ports:
- "6379:6379"
restart: always
backend:
build: .
container_name: node-backend
ports:
- "${PORT}:${PORT}"
depends_on:
- redis
env_file:
- .env
This code defines a docker-compose.yml
configuration file for setting up two services: a Redis server and a backend Node.js application. The Redis service uses the latest Redis image, exposing port 6379 and ensuring the container always restarts. The backend service is built from the current directory (.
) using a Dockerfile, exposes a dynamic port based on the environment variable ${PORT}
, and depends on the Redis service. Additionally, the backend container loads environment variables from a .env
file. The depends_on
directive ensures Redis starts before the backend service.
Build image and deploy containers:
docker-compose up --build -d

Make sure both containers are running by using docker ps
. Finally, type the following URL in any browser to fetch the prices: http://localhost:3000/prices
.

Backend side is ready!
iOS Sample App
The iOS sample app fetches product prices and allows users to update any price.

Core app functionallity is following:
import Foundation
struct Product: Codable, Identifiable, Sendable {
var id: String { product }
let product: String
var price: String
}
@MainActor
class PriceService: ObservableObject {
@Published var products: [Product] = []
func fetchPrices() async {
guard let url = URL(string: "http://localhost:3000/prices") else { return }
do {
let (data, _) = try await URLSession.shared.data(from: url)
let decoder = JSONDecoder()
if let productos = try? decoder.decode([Product].self, from: data) {
self.products = productos
}
} catch {
print("Error fetching prices:", error)
}
}
func updatePrice(product: String, price: String) async {
guard let url = URL(string: "http://localhost:3000/price") else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let body: [String: String] = ["product": product, "price": price]
request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: [])
do {
let (_, _) = try await URLSession.shared.data(for: request)
await self.fetchPrices()
} catch {
print("Error updating price: \(error.localizedDescription)")
}
}
}
This Swift code defines a model and a service for fetching and updating product prices. The Product
struct represents a product with an ID, name, and price, conforming to the Codable
, Identifiable
, and Sendable
protocols. The PriceService
class is an observable object that manages a list of products and provides methods for interacting with a remote server.
The fetchPrices()
function asynchronously fetches product prices from a specified URL (http://localhost:3000/prices
) using URLSession
, decodes the returned JSON into an array of Product
objects, and updates the products
array. The updatePrice(product:price:)
function sends a POST request to update the price of a specific product by sending a JSON payload to http://localhost:3000/price
. After a successful update, it calls fetchPrices()
again to refresh the list of products. Both functions handle errors by printing error messages if the network request fails.

In this review, we update the price using the app and then return to the browser to verify that the price update has been reflected in the Redis server.
Conclusions
In this project, we demonstrated how easy it is to set up a sample backend server using Redis and interact with it through an iOS app as the client.
You can find source code used for writing this post in following repository.