Cloud service

Calljmp provides a cloud service that allows mobile developers to create, deploy, and manage backend services on the cloud. This documentation will guide you through the process of developing your cloud service locally, managing secrets, working with the database, deploying the service, and calling it from your mobile app.

Getting started

To get started with the Calljmp cloud service, you need to install the Calljmp CLI. The CLI is a command-line tool that allows you to create, deploy, and manage your cloud services. You can install the CLI using npm:

npm install -g @calljmp/cli

Learn more about setup and usage of the CLI in the CLI documentation.

Create a service

Calljmp allows you to write cloud functions that can be triggered by your mobile app or other backend systems. Here’s an example of creating a basic cloud service.

./src/service/main.ts
import { CloudService } from './service'

class AppCloudService extends CloudService {
  protected override onRequest(request: Request): Response | Promise<Response> {
    return new Response(JSON.stringify({ message: 'Hello, world!' }), {
      headers: { 'Content-Type': 'application/json' },
    })
  }
}

export default new AppCloudService()

Develop locally

To run your service locally, you can use the Calljmp CLI. The CLI provides a command to start your service in development mode. This allows you to test your service locally before deploying it to the cloud.

  • Name
    project
    Type
    string
    Default value
    .
    Description

    Project directory. The property can be set using the CALLJMP_PROJECT environment variable.

  • Name
    module
    Type
    string
    Default value
    ./src/service
    Description

    Path to the module directory containing your service code in main.ts. The property can be set using the CALLJMP_MODULE environment variable.

  • Name
    port
    Type
    number
    Default value
    8787
    Description

    Port to run the service on.

calljmp start

After running the command, you should see output indicating that your service is running locally.

[calljmp] Ready on http://0.0.0.0:8787
[calljmp] - http://127.0.0.1:8787
[calljmp] - http://192.168.0.0:8787
[calljmp] Press Ctrl+C to stop the server

Test the service

You can test the function by sending a GET request to the /hello endpoint. You can use a tool like Postman or curl to test the endpoint.

GET
/
curl -X GET http://127.0.0.1:8787

The response should be a JSON object with a message:

{ "message": "Hello, world!" }

Automatic reload

When you run your service locally, you can make changes to your code and see the results immediately. The Calljmp CLI provides a development mode that automatically reloads your service when you make changes to the code. This allows you to develop and test your service without needing to restart it manually.

After making changes to your code, you should see output indicating that the service has been reloaded:

[calljmp] Detected changes, restarting...
[calljmp] Ready on http://127.0.0.1:8787
[calljmp] Press Ctrl+C to stop the server

Deployment

Once your cloud service is ready and fully tested locally, you can deploy it to the Calljmp cloud.

To deploy your service to the cloud, run the following command:

calljmp service deploy

This will deploy your local service, environment variables, to the cloud. Once deployed, you can access your service using the URL provided in the deployment output.

 Build completed
 Deployment completed
[calljmp] Access your service at: https://api.calljmp.com/target/v1/service

Test deployment

Each deployment is protected by only allowing requests from authorized clients. To test your deployment, you can request a short-lived token that can be used to call your service:

calljmp service access

It should print access token, copy the token and use it to call your service:

curl https://api.calljmp.com/target/v1/service --header "X-Calljmp-Platform: iOS" --header "Authorization: Bearer [ACCESS_TOKEN]"

Environment variables

Environment variables are used to store sensitive information, such as API keys and database connection strings, that should not be hardcoded in your service code. Calljmp provides two ways to manage environment variables: variables and secrets. Secrets are encrypted and stored securely, while variables are stored in plain text.

Environment variables are managed in the .env and .service.env files. You can add your environment variables there, and they will be available in your code.

CALLJMP_SOME_TOKEN = "not a secret token"
CALLJMP_SECRET_ENCRYPTED_TOKEN = "encrypted secret token"

To add a secret prepend SECRET_ to a variable name. This will encrypt the variable and make it available in your code without the prefix.

Upon each deployment, the variables and secrets are automatically added to the cloud service. Only the variables are synchronized with the cloud service. The secrets are added only if they are not already present in the cloud service. If you want to delete a secret, you can do so using the CLI or the dashboard.

Code generation

Environment variables are automatically loaded from local files. But in order to use them in your code, you need to generate a file with the types of the variables:

calljmp service generate
./src/service/main.ts
import { CloudService } from './service'

class AppCloudService extends CloudService {
  protected override onRequest(request: Request): Response | Promise<Response> {
    return new Response(
      JSON.stringify({
        development: this.context.development,
        variable: this.variables.someToken,
        secret: this.secrets.encryptedToken,
      }),
      {
        headers: { 'Content-Type': 'application/json' },
      }
    )
  }
}

export default new AppCloudService()

Security and trust

The service can be called from any client, including mobile apps and web apps. To ensure that only authorized clients can call your service, you can use arguments that are accessible through incoming http request.

  • Name
    userId
    Type
    string | undefined
    Description

    The user ID of the authenticated user. This is set to the user ID of the authenticated user, or undefined if the user isn't authenticated yet.

  • Name
    platform
    Type
    Platform
    Description

    Identifies the client platform (e.g., iOS, Android). This enum value indicates which platform is making the request.

  • Name
    serviceId
    Type
    string
    Description

    A unique identifier for your service. This string helps identify which specific service is being accessed.

  • Name
    development
    Type
    boolean
    Description

    Indicates if the service is running in development mode. This is useful for debugging and testing purposes.

./src/service/main.ts
import { CloudService } from './service'

class AppCloudService extends CloudService {
  protected override onRequest(request: Request): Response | Promise<Response> {
    if (!this.context.userId) {
      // User is not authenticated.
      return new Response(JSON.stringify({ error: 'Unathenticated' }), {
        status: 401,
        headers: { 'Content-Type': 'application/json' },
      })
    }

    // User is authenticated and the device and app integrity is confirmed.

    return new Response(
      JSON.stringify({
        development: this.context.development,
        variable: this.variables.someToken,
        secret: this.secrets.encryptedToken,
      }),
      {
        headers: { 'Content-Type': 'application/json' },
      }
    )
  }
}

export default new AppCloudService()

Database

Calljmp provides a database that is automatically created and managed for you. You can use the database to store and retrieve data from your service. The database is a SQLite database that is optimized for performance and scalability.

./src/service/main.ts
import { CloudService } from './service'

class AppCloudService extends CloudService {
  protected override onRequest(request: Request): Response | Promise<Response> {
    const result = await this.database.prepare('SELECT * FROM users').all()
    return new Response(JSON.stringify(result), {
      headers: { 'Content-Type': 'application/json' },
    })
  }
}

export default new AppCloudService()

You can also integrate custom ORMs or wrapper libraries to enhance your database experience. For example, you can use Kysely or Drizzle to create a type-safe database layer on top of the Calljmp SQLite database. You find more options in the Cloudflare D1 community projects.

Synchronizing database

Calljmp provides a built-in database synchronization feature that allows you to synchronize your local database with the cloud database. This is useful for testing and development purposes.

To synchronize your local database with the cloud database, you can reset your local database and pull the cloud database or generate needed migrations via schema management on your local machine. This will ensure that your local database is in sync with the cloud database.

calljmp database reset

We recommend using the schema command to process your database schema and generate migrations.

calljmp database schema

Migrations

Calljmp provides a built-in migration system that allows you to manage your database schema changes. You can create and run migrations using the Calljmp CLI.

Create a new migration .sql file in the migrations directory:

mkdir -p ./src/service/migrations
touch ./src/service/migrations/0001-create-products.sql

You can modify migrations folder structure to your liking. The only requirement is that the migration files must be named in the format 0001-create-products.sql, where 0001 is the migration number and create-products is a descriptive name for the migration.

./src/service/migrations/0001-create-products.sql
CREATE TABLE IF NOT EXISTS products (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  name TEXT NOT NULL,
  price REAL NOT NULL
);

To run the migration, you can use the Calljmp CLI:

  • Name
    project
    Type
    string
    Default value
    .
    Description

    Project directory. The property can be set using the CALLJMP_PROJECT environment variable.

  • Name
    migrations
    Type
    string
    Default value
    ./src/service/migrations
    Description

    Path to the migrations directory. The property can be set using the CALLJMP_MIGRATIONS environment variable.

  • Name
    remote
    Type
    boolean
    Default value
    false
    Description

    Indicates if the migration should be run on the remote service.

calljmp migrate

Integrations

Calljmp supports integration with third-party frameworks, such as the Hono framework. Hono is a high-performance, lightweight web framework designed for building APIs and microservices. It offers a clean and intuitive API for defining routes, middleware, and error handling, making it an excellent choice for developers seeking simplicity and efficiency in their service architecture.

To use Hono with Calljmp, you can create a new service using the Hono framework and deploy it to the Calljmp cloud. Here’s an example of creating a simple Hono service:

./src/service/main.ts
import { Context, Hono } from 'hono'
import { poweredBy } from 'hono/powered-by'
import { bodyLimit } from 'hono/body-limit'
import { timeout } from 'hono/timeout'
import { CloudService } from './service'

interface CloudServiceApp {
  Bindings: CloudServiceEnv
}

class AppCloudService extends CloudService<CloudServiceApp['Bindings']> {
  private _app = new Hono<CloudServiceApp>()
    .use(timeout(30_000))
    .use(bodyLimit({ maxSize: 100 * 1024 }))
    .use(poweredBy({ serverName: 'App' }))
    .get('/', this.handle.bind(this))

  protected override onRequest(
    request: Request,
    env?: CloudServiceEnv,
    executionCtx?: ExecutionContext
  ): Response | Promise<Response> {
    return this._app.fetch(request, env, executionCtx)
  }

  private async handle(c: Context<CloudServiceApp>) {
    const result = await this.database.prepare('SELECT * FROM users').all()
    return c.json(result)
  }
}

export default new AppCloudService()