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 function to fetch user data.

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

const service = Service()

service.get('/hello', async c => {
  return c.json({
    message: 'Hello, world!',
  })
})

export default service

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
/hello
curl -X GET http://127.0.0.1:8787/hello

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 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] Service deployed with UUID: 3b6fa7dc-b458-4855-bf76-d034c7576693
[calljmp] Access your service at: https://api.calljmp.com/target/v1/service/3b6fa7dc-b458-4855-bf76-d034c7576693

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 generate
./src/service/main.ts
import { Service } from './service'

const service = Service()

service.get('/hello', async c => {
  return c.json({
    development: c.env.DEVELOPMENT,
    variables: c.env.SOME_TOKEN,
    secrets: c.env.ENCRYPTED_TOKEN,
  })
})

export default service

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
    trusted
    Type
    boolean
    Description

    Indicates if the request comes from a client that has passed the integrity check. Set to true when Calljmp has verified the client's integrity. Note that false doesn't necessarily mean the client is malicious - it may be that the integrity check hasn't completed yet or the user isn't authenticated. Always verify this flag for sensitive operations.

  • 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.

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

const service = Service()

service.get('/hello', async c => {
  if (!c.var.trusted) {
    // Calljmp was not able to confirm the device and app integrity when the service was called.
    return c.json({ error: 'Unauthorized' }, 401)
  }

  if (!c.var.userId) {
    // User is not authenticated.
    return c.json({ error: 'Unathenticated' }, 401)
  }

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

  return c.json({
    variables: c.env.SOME_TOKEN,
    secrets: c.env.ENCRYPTED_TOKEN,
  })
})

export default service

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 { Service } from './service'

const service = Service()

service.get('/', async c => {
  const result = await c.env.db.prepare('PRAGMA table_info(users)').run()
  return c.json(result)
})

export default service

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 to your local machine. This will ensure that your local database is in sync with the cloud database.

calljmp reset
  • Name
    project
    Type
    string
    Default value
    .
    Description

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

  • Name
    migrations-table
    Type
    string
    Description

    Name of the table used to store the migration history. Useful if you are using ORM library that manages migrations for you.

  • Name
    table-data
    Type
    string
    Description

    Name of table to pull data from. It can be specified multiple times to pull from multiple tables.

calljmp pull

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

Barebones

Calljmp uses the Hono framework to build cloud services. Hono is a fast and lightweight web framework for building APIs and microservices. It provides a simple and intuitive API for defining routes, middleware, and error handling. However, if you prefer to use another framework or no framework at all, you can do so by creating a custom service with fetch listener.

First regenerate the code to remove the Hono types:

calljmp generate --no-hono

Then create a custom service similar to the one above:

./src/service/main.ts
import { ServiceArgs, Env } from './service'

export default {
  async fetch(request: Request, env: Env) {
    // Arguments are only available in production.
    const { args } = ServiceArgs.tryFrom(request)

    const url = new URL(request.url)
    const path = url.pathname

    // Simulate a service response
    if (path === '/hello') {
      if (!args || !args.trusted) {
        // Calljmp was not able to confirm the device and app integrity when the service was called.
        return new Response(JSON.stringify({ error: 'Unauthorized' }), {
          status: 401,
          headers: { 'Content-Type': 'application/json' },
        })
      }

      if (!args || !args.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({
          variables: env.SOME_TOKEN,
          secrets: env.ENCRYPTED_TOKEN,
        }),
        {
          headers: { 'Content-Type': 'application/json' },
        }
      )
    }

    // Fallback for other paths
    return new Response('Not Found', { status: 404 })
  },
}