Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial version of Docs #41

Merged
merged 12 commits into from
Jan 11, 2024
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"label": "Tutorial - Extras",
"label": "Functions",
"position": 3,
"link": {
"type": "generated-index"
Expand Down
99 changes: 99 additions & 0 deletions docs/functions/data-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
---
sidebar_position: 6
---

# Use Custom Data Types

With Celest serverless APIs and functions, serialization is handled out-of-the-box in most cases. In situations requiring custom serialization, we support any custom classes that you’re already using without any extra setup.

Imagine you're working on an e-commerce application with an `Order` class defined in your codebase.

```dart
class Order {
const Order ({
required this.id,
required this.customerName,
required this.price,
});

final int id;
final String customerName;
final Price price;
}

enum Currency { usd, cad, ... }

class Price {
const Price({
required this.currency,
required this.dollars,
required this.cents,
}): assert(cents < 100);

final Currency currency;
final int dollars;
final int cents;
}
```

You can use this `Order` type in any cloud function as both a parameter or return value, without the need to manually add serialization logic.

```dart
import 'package:celest/celest.dart';

import 'types/order.dart';

Future<String> createOrder(
FunctionContext context,
Order customerOrder,
) async {
// ...
}
```

When communicating with your backend, Celest will serialize the `Order` class as a JSON map with the field names as keys.

```json
{
"id": 123,
"customerName": "Celest",
"price": {
"currency": "usd",
"dollars": 100,
"cents": 34
}
}
```

If you need custom handling over serialization logic, add a `fromJson` constructor and `toJson` method to your datatype. Celest will use your custom `fromJson`/`toJson` implementations instead when transmitting the type to and from your backend.
tadaspetra marked this conversation as resolved.
Show resolved Hide resolved

Note

Here, the `Price.toJson` method is used to upper-case the `currency` value.
```dart
class Price {
// ...

factory Price.fromJson(Map<String, dynamic> json) {
// ...
}

Map<String, dynamic> toJson() => {
'currency': currency.name.toUpperCase(),
'dollars': dollars,
'cents': cents,
};
}
```

```json
{
"id": 123,
"customerName": "Celest",
"price": {
"currency": "USD",
"dollars": 100,
"cents": 34
}
}
```
69 changes: 69 additions & 0 deletions docs/functions/env-variables.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
sidebar_position: 8
---

# Set the Environment Variables
abdallahshaban557 marked this conversation as resolved.
Show resolved Hide resolved
abdallahshaban557 marked this conversation as resolved.
Show resolved Hide resolved

Environment variables can be used to provide environment-specific configuration to your backend. They allow you to keep their values separate from your codebase, improving flexibility when running in different environments.

To set up environment variables in your backend, navigate to the `<flutter_app>/celest/config/env.dart` file and list all the variables you’ll need throughout your backend.

```dart
import 'package:celest/celest.dart';

const EnvironmentVariable greetingUrl = EnvironmentVariable(name: 'GREETING_URL');
```

To ensure a cloud function has access to the variable when it runs, pass it as a parameter and annotate with the variable definition. Here, the greeting service URL will be securely injected by the server when your function starts.

Note

Annotated parameters (like `greetingUrl`) will not appear in the generated client, but can be used in your backend when unit testing and mocking (see **Testing your backend resources** below).
```dart
import 'package:celest/celest.dart';
import 'package:http/http.dart' as http;

import '../resources.dart';

Future<String> sayHello(
FunctionContext context,
String name, {
@envVariables.greetingUrl required String greetingUrl,
}) async {
// Call an external greeting service.
final response = await http.post(
Uri.parse(greetingUrl).replace(path: '/sayHello'),
body: jsonEncode({
'name': name,
}),
);
if (response.statusCode != 200) {
throw GreetingException(
'Failed to say hello to $name: ${response.body}',
);
}
return response.body;
}

class GreetingException implements Exception {
const GreetingException(this.message);

final String message;
}
```

### Setting up environment variable values locally
When you run `celest start` or `celest deploy`, the CLI will look for values of the environment variables in your shell environment. For any variables not defined, the CLI will prompt you for their values.

```shell
Please provide values for the following environment variables:
? GREETING_URL: <Enter your value>
```

To change the values of environment variables previously defined, re-export the value from your terminal before running `celest start` or `celest deploy`.

```shell
export GREETING_URL=<new URL>
```

Celest will detect the presence of a new value and update your local/deployed function to reflect the change.
52 changes: 52 additions & 0 deletions docs/functions/exceptions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
sidebar_position: 7
---

# Create Custom Exceptions

You can create custom exception types in your backend to control how APIs and cloud functions behave when there are errors. This enables you to have clear exceptions thrown in your Flutter app that you can react to.

Below is an example of how to define a custom exception. You can create exceptions in any folder inside your `celest` folder. For this example, the exception type is defined in `<flutter_app>/celest/apis/my_exception.dart`.

```dart
class MyException implements Exception {
const MyException(this.message);

final String message;
}
```

You can then throw these exceptions in your functions whenever needed as shown below.

```dart
import 'package:celest/celest.dart';

import 'my_exception.dart';

Future<String> sayHello(
FunctionContext context,
String name,
) async {
// Perform custom validation
if (name.isEmpty) {
throw MyException('Input cannot be empty');
}
return 'Hello, $name';
}
```

In your Flutter app, the same `MyException` type will be thrown by the generated client if an error occurs.

```dart
import 'celest/client.dart' as celest;

Future<String> getGreeting(String name) async {
try {
return await celest.apis.greeting.sayHello(name);
// Catch the exception type defined in your backend
} on MyException catch (e) {
print('Uh oh! Could not greet $name: $e');
rethrow;
}
}
```
66 changes: 66 additions & 0 deletions docs/functions/function.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
sidebar_position: 3
---

# Create a Function

Creating serverless APIs and functions with Celest enables you to connect and aggregate information from different parts of your backend, and build custom business logic that runs completely in the cloud. You define your cloud functions as Dart functions, and Celest takes care of setting up and managing the backend infrastructure around them.

To get started with building your first API, navigate to the `<flutter_app>/celest/apis/` folder and create a file named `<api_name>.dart`. You can create as many API files as you want in this directory.


Tip

Access to your APIs is denied by default. What this means is that you’ll need to add the `@api.anonymous()` annotation to the top of the file for APIs to be publicly accessible.

```dart
// Enables public access to the API.
@api.anonymous()
library;

import 'package:celest/api.dart' as api;

Future<String> sayHello(
FunctionContext context,
String name,
) async {
return 'Hello, $name';
}

Future<String> sayGoodbye(
FunctionContext context,
String name,
) async {
return 'Goodbye, $name';
}
```

The above code snippet is all you need to define your cloud functions! When the `celest start` command runs, a local environment is spun up and a Dart client is generated to help you connect to the local backend.

Below is an example of how you would use the generated client in your `main.dart` file.

```dart
import 'package:flutter/material.dart';
import 'package:flutter_app/celest/client.dart' as celest;

void main() {
celest.init();
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return FutureBuilder(
// Call your cloud function like a normal Dart function!
future: celest.apis.greeting.sayHello('Celest'),
builder: (_, snapshot) => switch (snapshot) {
AsyncSnapshot(:final data?) => Text(data),
_ => const CircularProgressIndicator(),
},
);
}
}
```
85 changes: 85 additions & 0 deletions docs/functions/get-started.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
---
sidebar_position: 2
---

# Get Started
tadaspetra marked this conversation as resolved.
Show resolved Hide resolved

## Prerequisites
To use Celest in your Flutter app, you need the following prerequisites:

1. Install Flutter
2. Start a new fluter project using the `flutter create` command
3. Install the Celest CLI using a shell command

Note

This is an example and will not work just yet. Join the [waitlist](https://celest.dev) to get access when we launch!

```shell
$ curl --proto '=https' --tlsv1.2 https://install.celest.dev | sh
```

That’s it! You do not need any additional tooling to build, test, and deploy your backend.

## Setting up the Celest CLI
After installing the Celest CLI, navigate to the root of your Flutter project and run the following command.

```shell
$ celest start
```

You will be prompted to sign in using GitHub. Once the authentication with GitHub is successful, a watch command will continue to run in your CLI to detect changes made to your Celest backend definition and code-generate a Dart client for you in the following path `<flutter_app>/lib/celest/client.dart` to test your changes locally. We will cover later how to use the code-generated client after defining your APIs and cloud functions.

The CLI will also create a folder in your project called `celest`, which will include the following files.

```shell
flutter_app/
└── celest/
├── apis/
│ ├── greeting.dart # a serverless API definition
│ └── middleware.dart # middleware definitions
└── config/
└── env.dart # environment variables
```


## Deploying your backend resources
When you have tested and validated your backend locally, use the Celest CLI to deploy your backend resources to the cloud.

```shell
$ celest deploy
```

### Calling Celest APIs with HTTP requests
tadaspetra marked this conversation as resolved.
Show resolved Hide resolved
If you'd like to use your Celest APIs outside of your Flutter/Dart app, you still can! Celest functions are exposed as HTTP endpoints which can be called via any HTTP client from any programming language or toolchain.

The HTTP conventions for Celest functions are:

* JSON requests/responses
* POST requests
* 200 status code for success
* 400 status code for exceptions
* 500 status code for errors

When a cloud function fails with an exception or error, the response will carry a 4xx/5xx status code and JSON body with an `error` key. If the exception is a user-defined exception type, the `error` field itself is encoded as a JSON message.

For example, if a function throws the `MyException` type defined in the example above, the response would be:

```
400 Bad Request
{
"error": {
"$type": "MyException",
"message": "Input cannot be empty"
}
}
```

However, if the function threw a `StateError`, it would look like this where the error is stringified in the `error` field.

```
500 Internal Server Error
{
"error": "Bad state: Something bad happened"
}
```
7 changes: 7 additions & 0 deletions docs/functions/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
sidebar_position: 1
---

# Introduction
abdallahshaban557 marked this conversation as resolved.
Show resolved Hide resolved

Functions are written in Dart, but run in the cloud.
Loading