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.
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 theCALLJMP_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.
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.
Both .env
and .service.env
can be split per environment. For example, you
can have .env.development
, .env.production
, and
.service.env.development
, .service.env.production
. The CLI will
automatically pick the right file based on the environment you are running in.
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
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.
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.
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
The reset
command will delete your local database and create a new one. This
is useful for testing and development purposes.
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.
The migration number must be unique and incremented for each new migration. The migration name should be descriptive and indicate the purpose of the migration.
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
By default migrations are run locally. To run them on the remote service, use
the --remote
flag. If you like to build fresh local database similar to the
remote service, you can use combination of reset
, pull
, and migrate
commands.
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:
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()