221 | The Pro tier includes 3 premium projects and unlimited free
222 | projects. Free projects have limited resources and cold starts, but
223 | cost you nothing to run.
224 |
225 |
226 |
227 | Every premium project comes with an included amount of usage. If
228 | your project scales beyond these limits, you pay for any additional
229 | resources as you go.
230 |
6 |
7 | import BlogIndex from '@components/BlogIndex'
8 |
9 |
10 |
--------------------------------------------------------------------------------
/pages/blog/how-we-want-to-backend.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: How we want to backend
3 | description: How we reason about Celest's design and our wants for the future of backend development.
4 | date: May 28, 2024
5 | order: 1
6 | ---
7 |
8 | import { Callout } from 'nextra/components';
9 | import { Bleed } from 'nextra-theme-docs';
10 | import Image from 'next/image';
11 |
12 | import Authors, { Author } from '@components/Authors'
13 | import annotateAllTheThings from '@/public/img/annotate-all-the-things.jpeg';
14 |
15 | # How we want to backend
16 |
17 |
18 |
19 | Today, we're excited to release version 0.4 of Celest, including support for HTTP customization, improved ergonomics, and a preview of running
20 | Flutter and UI code in the sky! We've published a separate [blog](/blog/fluttering-in-the-sky) post to discuss all the
21 | new features. In this post, we want to dive into more of the design of Celest and the principles that guide it.
22 |
23 |
24 |
25 | As you build with Celest, you'll notice that it requires a slightly different way of thinking about your backend.
26 | This article walks through how we arrived at the design of Celest and what we're trying to achieve.
27 |
28 | The first thing you'll notice is Celest's liberal usage of Dart [annotations](https://dart.dev/language/metadata).
29 | Annotations are used in Celest to convey almost all the metadata and semantics of your backend.
30 |
31 | ```dart
32 | @cloud
33 | @authenticated
34 | Future sayHello({
35 | @principal required User user,
36 | }) async {
37 | return 'Hello, ${user.displayName}';
38 | }
39 | ```
40 |
41 | At first glance, this syntax can seem cumbersome and you may wonder why this design decision was made.
42 |
43 | Our goals for Celest are two-fold:
44 | 1. To have you write the least amount of boilerplate code possible, letting us do the heavy lifting; and
45 | 2. To let you convey the semantics of your backend precisely and unambiguously.
46 |
47 | Annotations allow us to achieve both these goals, _if_ we do it right.
48 |
49 | > And if we ever don't, please let us know! API design and naming can be especially tricky to get right,
50 | > and we're by no means perfect.
51 |
52 | ## Declarative backends
53 |
54 | The first thing annotations do is enable you to think about your backend declaratively. Instead of telling
55 | Celest how to do something, you tell Celest what you want to do and let it figure out the best way possible.
56 |
57 | For example, instead of writing code to set up an HTTP server, you annotate your logic with `@cloud`.
58 |
59 | ```dart
60 | @cloud
61 | Future sayHello() async {
62 | return 'Hello, world!';
63 | }
64 | ```
65 |
66 | Instead of writing code to handle authentication via middleware or a proxy, you label your protected logic
67 | with `@authenticated`.
68 |
69 | ```dart
70 | @cloud
71 | @authenticated
72 | Future sayHello() async {
73 | return 'Hello, valued customer!';
74 | }
75 | ```
76 |
77 | Celest has built-in opinions on how to handle each of these cases which optimize the performance and security of your backend.
78 | In the cases where you know better than us, we expose additional annotations to override our defaults (e.g. with
79 | [HTTP customizations](/docs/functions/http/customization)) while stll minimizing the boilerplate.
80 |
81 | Either way, Celest allows you to convey the _intent_ of how some code should work without worrying about
82 | exactly _how_ it is done unless you want to.
83 |
84 | ### Sane defaults
85 |
86 | Being so opinionated means that if we have the wrong opinions, you'll end up writing customizations for everything you're building,
87 | completely defeating our sole mission. To avoid this, we've tried to make our default behavior _sane_.
88 |
89 | Sane default behavior means that the "magic" Celest does is what you probably would have done anyway. It's what your intuition would suggest is
90 | happening. And it has baked in the current best practices around design and security.
91 |
92 | Let's take `@authenticated` as an example. When you see a function labeled as such, you would expect that only users who have passed through a login
93 | flow, and have a valid login session, can access it. You would intuit this information is being conveyed via some HTTP header like `Authorization`. And
94 | best practices would suggest that any function _not_ labeled as `@authenticated` be deny-by-default.
95 |
96 | All of this is true for `@authenticated` and these are the ways we try to reason about every annotation.
97 |
98 | ## Infrastructure from code
99 |
100 | When it comes time to deploy that backend, the current trend is to write your infrastructure as code (the IaC paradigm) in order to have declarative,
101 | reproducible, and auditable deployments. To do this, you would typically introduce a new codebase to store your infrastructure which lives alongside your backend.
102 |
103 | For example, you may have a backend written in Node.js and scaffold out your infrastructure in Terraform.
104 |
105 |
106 |
An example Node.js Lambda function and the Terraform to deploy it.
126 |
127 |
128 | Much like Celest, this allows you to _declare_ the components of your infrastructure instead of imperatively creating, updating, and deleting
129 | them by hand. And for a while, it works great!
130 |
131 | The split of backend and infrastructure logic creates fragmentation in your codebase, though. As your project scales, some hairy issues start to emerge:
132 |
133 | - Features must be written and coordinated twice: once in the backend and again for the corresponding infrastructure.
134 | - The infrastructure code can become out-of-date with the backend code, leading to bugs and security vulnerabilities.
135 | - Understanding the infrastructure that controls a piece of backend code requires jumping between two, possibly distinct, codebases.
136 |
137 | At Celest, we've experienced all these problems and more. And through that pain, we've come to believe that the best way to write infrastructure is to
138 | write it _alongside_ the code it manages. Ideally, everything relevant to a piece of backend code should live right next to it
139 | in the same file, function, and language.
140 |
141 | > This paradigm has been coined infrastructure-**from**-code (IfC), as opposed to the
142 | > infrastructure-as-code pattern described previously.
143 |
144 | ```dart
145 | @cloud
146 | @authenticated
147 | Future sayHello() async {
148 | return 'Hello, valued customer!';
149 | }
150 | ```
151 |
152 | Adopting this paradigm, you need only look at the function itself to understand both its logic and its infrastructure.
153 | No other files, languages, or teams are required.
154 |
155 | On the surface, it may seem that this leads to more friction when trying to understand any piece of backend code. But because there still exists
156 | a clean separation of your backend and infrastructure (i.e. runtime vs. static code), you can safely ignore one or the other to process each part in isolation.
157 |
158 | And whenever you need to update a part of your backend, you can do so in a single place.
159 |
160 | We think that's powerful!
161 |
162 | ## Blurring the lines even more
163 |
164 | But why stop there? What if you could not only keep your backend and infrastructure code together but also your client code?
165 |
166 | Currently, in Celest, everything must be done in a separate `celest` folder which lives outside your client app. Your backend types and logic are all
167 | separate from your client code and, because they live in a separate Dart package, you must follow Dart package conventions to share them with your frontend.
168 |
169 | For example, Celest requires that all your shared code live in the `lib` folder of your Celest project. But this, too, can create friction when you're
170 | working on a piece of frontend code and realize you need to update the backend.
171 |
172 | Imagine you're working on this Dart application and decide you want the `sayHello` function to run only in the backend.
173 |
174 | ```dart
175 | Future sayHello() async {
176 | return 'Hello, world!';
177 | }
178 |
179 | void main() async {
180 | print(await sayHello());
181 | }
182 | ```
183 |
184 | You would have to move the function to your Celest project, import the generated client into your frontend project, and update your frontend to call the Celest client.
185 | Hopefully, it all goes as planned.
186 |
187 | What if, instead, you could just do this?
188 |
189 | ```dart {1}
190 | @cloud
191 | Future sayHello() async {
192 | return 'Hello, world!';
193 | }
194 |
195 | void main() async {
196 | print(await sayHello());
197 | }
198 | ```
199 |
200 | That's how we want to backend. Over the next few months, you'll see more work to blur these lines with IDE plugins, better Dart analyzer support, and macros 👀. To learn
201 | more about the features available today with Celest 0.4, check out the launch [post](/blog/fluttering-in-the-sky).
202 |
203 | We'd love to hear your thoughts on this vision for backend development. Reach out to us on [GitHub](https://github.com/celest-dev/celest), [X](https://x.com/celest_dev), and [Discord](/discord)!
204 |
205 | Dart on! 🚀
206 |
--------------------------------------------------------------------------------
/pages/blog/local-iterations-release.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | slug: local-iterations-release
3 | title: First CLI release for local iterations
4 | description: "It's with immense excitement that we write our first blog post announcing the first release of the Celest CLI, enabling you to experience Celest and build a backend locally!"
5 | date: January 22, 2024
6 | ---
7 |
8 | import Authors, { Author } from '@components/Authors'
9 |
10 | # First CLI release for Local Iterations
11 |
12 |
13 |
14 | It's with immense excitement that we write our first blog post announcing the first release of the Celest CLI, enabling you to experience Celest and build a backend locally! But today is not just about introducing our product—it’s about sharing our dream and vision with the community that we cherish deeply.
15 |
16 | Celest is a backend-as-a-service that enables Flutter developers to build their backends in Dart, without any additional tooling. With this release, we are making our CLI and Celest Functions available to you to build and experiment with locally. In response to the excited reaction we’ve received from the Flutter community, we chose to release this version now so that we can give you a taste of the Celest experience and get your feedback early.
17 |
18 | Check out our video that walks you through the Celest experience!
19 |
20 |
21 |
22 |
23 |
24 | ## Why use Celest?
25 |
26 | Celest and its Functions offering enable you to write custom business logic in Dart that runs in the cloud. Celest Functions is the first "Cloud Widget" we are introducing to help bridge the gap of building backend components completely in Dart. Since Celest Functions are just Dart functions, they enable you to reuse code between your Flutter app and your backend with full type safety between the two. All of the third-party packages you're familiar with from the Dart ecosystem can now be used in your backend.
27 |
28 | With Celest Functions and the CLI, you get a type-safe API layer between your business logic in the cloud and the UI of your Flutter app. When developing locally, your changes are available instantly via our hot-reload mechanism. This makes it easier to iterate quickly while adding features to your backend and ship to your customers _fast_.
29 |
30 | In future releases, Celest will handle the deployment and management of your functions' underlying infrastructure.
31 |
32 | ## What's next for Celest?
33 |
34 | We can't wait to share more updates and continue building Celest with you. We’re on this adventure together. Your thoughts, feedback, and ideas are what will shape Celest’s future the most.
35 |
36 | Our next action items are creating a public roadmap, RFCs for Authorization and Real-Time Patterns, and enabling cloud deployments for all Celest users.
37 |
38 | Visit our [documentation](/docs) to learn more about this new release and to get started with the CLI and Celest Functions. Join our [Discord server](/discord) to give us feedback about your experience with this or future releases and connect with other Flutter and Dart enthusiasts. And let us know on [GitHub](https://github.com/celest-dev/celest) if there are any features you'd like to see or if you get stuck at any point.
39 |
40 |
15 |
16 | Cloud Auth is built on top of the [Cedar](https://www.cedarpolicy.com/en) policy engine and [SQLite](https://www.sqlite.org/) to both manage user accounts
17 | and enforce consistent access controls in a single Dart package. The Celest CLI will automatically integrate Cloud Auth into your project when
18 | using Celest Auth, but the service is exposed as a Shelf router and middleware which can be used in any Dart project.
19 |
20 | To get started with Cloud Auth, continue to the next page to see how to add it to your Celest project 🚀
21 |
22 | > Celest Auth also suppors integrating [Supabase](./auth/supabase-auth.mdx) and [Firebase](./auth/firebase-auth.mdx) for cases where you
23 | > already have an existing user base.
24 |
--------------------------------------------------------------------------------
/pages/docs/auth/_meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "celest-auth": "Celest Auth",
3 | "using-the-auth-client": "Using the Auth client",
4 | "--external": {
5 | "title": "External Providers",
6 | "type": "separator"
7 | },
8 | "firebase-auth": "Firebase Auth",
9 | "supabase-auth": "Supabase Auth"
10 | }
11 |
--------------------------------------------------------------------------------
/pages/docs/auth/celest-auth.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Celest Auth
3 | description: Learn how to use Celest's built-in Cloud Auth for authenticating users in your Celest project.
4 | ---
5 |
6 | import { Callout } from 'nextra-theme-docs'
7 |
8 | # Celest Auth
9 |
10 | Adding Auth to your Celest project takes 5 lines of code. In your `project.dart` file, add the following code:
11 |
12 | ```dart {5-9} copy filename="celest/lib/src/project.dart"
13 | import 'package:celest/celest.dart';
14 |
15 | const project = Project(name: 'my-project');
16 |
17 | const auth = Auth(
18 | providers: [
19 | AuthProvider.email(),
20 | ],
21 | );
22 | ```
23 |
24 | That's it! When you run `celest start`, Celest will automatically integrate our open-source Auth backend
25 | [Cloud Auth](https://github.com/celest-dev/celest/tree/main/services/celest_cloud_auth) and generate an Auth
26 | client for you to use in your Flutter app.
27 |
--------------------------------------------------------------------------------
/pages/docs/auth/firebase-auth.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Firebase Auth
3 | description: Learn how to use Firebase Auth for authenticating users in your Celest project.
4 | ---
5 |
6 | import { Callout } from 'nextra-theme-docs'
7 |
8 | # Firebase Auth
9 |
10 | If you're migrating to Celest from another framework, chances are you already have a user base that you'd like to use.
11 |
12 | As of Celest V1, Firebase can be used as your auth provider in Celest. And doing so is just as easy as with Celest's built-in auth.
13 |
14 | ## In your Celest project
15 |
16 | To use Firebase as your auth provider, add the following code to your `project.dart` file:
17 |
18 | ```dart copy filename="celest/lib/src/project.dart"
19 | import 'package:celest/celest.dart';
20 |
21 | const project = Project(name: 'firebase');
22 |
23 | const auth = Auth(
24 | providers: [
25 | ExternalAuthProvider.firebase(),
26 | ],
27 | );
28 | ```
29 |
30 | Verifying users with Firebase requires knowing your Firebase project ID. When you run `celest start`, Celest will automatically search your environment for
31 | any available Firebase projects. If it can't figure out which project to use, you'll be prompted to enter it manually.
32 |
33 | Celest will securely store your Firebase project ID for future use so it doesn't need to prompt you again.
34 |
35 | ## In your Flutter app
36 |
37 | In your Flutter app, Celest will listen for changes to Firebase's auth state and update the Celest state accordingly so that
38 | calls to your Cloud Functions are authenticated appropriately.
39 |
40 | To do so, it needs access to your Firebase client, which you pass into the `celest.init` call in your `main` function.
41 |
42 | ```dart copy filename="lib/main.dart"
43 | import 'package:firebase_auth/firebase_auth.dart';
44 | import 'package:firebase_core/firebase_core.dart';
45 | import 'package:flutter/material.dart';
46 | import 'package:my_project_client/my_project_client.dart';
47 |
48 | Future main() async {
49 | WidgetsFlutterBinding.ensureInitialized();
50 | await Firebase.initializeApp();
51 | celest.init(
52 | externalAuth: ExternalAuth.firebase(FirebaseAuth.instance),
53 | );
54 | runApp(const MyApp());
55 | }
56 | ```
57 |
58 |
--------------------------------------------------------------------------------
/pages/docs/auth/supabase-auth.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Supabase Auth
3 | description: Learn how to use Supabase Auth for authenticating users in your Celest project.
4 | ---
5 |
6 | import { Callout } from 'nextra-theme-docs'
7 |
8 | # Supabase Auth
9 |
10 | If you're migrating to Celest from another framework, chances are you already have a user base that you'd like to use.
11 |
12 | As of Celest V1, Supabase can be used as your auth provider in Celest. And doing so is just as easy as with Celest's built-in auth.
13 |
14 | ## In your Celest project
15 |
16 | To use Supabase as your auth provider, add the following code to your `project.dart` file:
17 |
18 | ```dart copy filename="celest/lib/src/project.dart"
19 | import 'package:celest/celest.dart';
20 |
21 | const project = Project(name: 'supabase');
22 |
23 | const auth = Auth(
24 | providers: [
25 | ExternalAuthProvider.supabase(),
26 | ],
27 | );
28 | ```
29 |
30 | Verifying users with Supabase requires knowing your Supabase project's URL. When you run `celest start`, Celest will automatically search your environment for
31 | any available Supabase projects. If it can't figure out which project to use, you'll be prompted to enter it manually.
32 |
33 | Celest will securely store your Supabase project URL for future use so it doesn't need to prompt you again.
34 |
35 | ## In your Flutter app
36 |
37 | In your Flutter app, Celest will listen for changes to Supabase's auth state and update the Celest state accordingly so that
38 | calls to your Cloud Functions are authenticated appropriately.
39 |
40 | To do so, it needs access to your Supabase client, which you pass into the `celest.init` call in your `main` function.
41 |
42 | ```dart copy filename="lib/main.dart"
43 | import 'package:flutter/material.dart';
44 | import 'package:my_project_client/my_project_client.dart';
45 | import 'package:supabase_flutter/supabase_flutter.dart';
46 |
47 | Future main() async {
48 | WidgetsFlutterBinding.ensureInitialized();
49 | final supabase = await Supabase.initialize(
50 | url: 'YOUR_SUPABASE_URL',
51 | anonKey: 'YOUR_SUPABASE_ANON_KEY',
52 | );
53 | celest.init(
54 | externalAuth: ExternalAuth.supabase(supabase.client.auth),
55 | );
56 | runApp(const MyApp());
57 | }
58 | ```
59 |
--------------------------------------------------------------------------------
/pages/docs/auth/using-the-auth-client.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Using the Flutter client
3 | description: Learn how to use the generated Flutter client to authenticate users in your app.
4 | ---
5 |
6 | ## Signing In
7 |
8 | To sign in a user, you can use the `authenticate` method on the `celest.auth` object. It's called `authenticate` because there is no longer a distinction between signing in or signing up!
9 | If the user doesn't exist, they will be created.
10 |
11 | ```dart
12 | final flow = await celest.auth.email.authenticate(
13 | email: 'dillon@celest.dev',
14 | );
15 |
16 | // Prompt user for OTP code sent to the email
17 |
18 | final user = await flow.verify(otpCode: '123456');
19 | print('User signed in: ${user.id}');
20 | ```
21 |
22 | A full example of a typical auth flow can be found in our [repo](https://github.com/celest-dev/celest/tree/main/packages/celest_auth/example).
23 |
24 | ## Listening for changes
25 |
26 | Celest provides a `Stream` interface to get notified when the state of the user changes. You should use this to update the UI when the user signs in or out and perform any other necessary actions.
27 |
28 | ```dart
29 | /// Shows the current user's information if they are signed in, automatically updating
30 | /// when the user signs in or out.
31 | class CurrentUser extends StatelessWidget {
32 | const CurrentUser({
33 | super.key,
34 | });
35 |
36 | @override
37 | Widget build(BuildContext context) {
38 | return StreamBuilder(
39 | stream: celest.auth.authStateChanges,
40 | builder: (context, snapshot) {
41 | if (snapshot.connectionState == ConnectionState.waiting) {
42 | return const CircularProgressIndicator();
43 | }
44 | final state = snapshot.data!;
45 | return switch (state) {
46 | Authenticated(:final user) => Text(
47 | 'Currently signed in: id=${user.userId}, '
48 | 'email=${user.primaryEmail?.email}',
49 | ),
50 | _ => const Text('Currently signed out'),
51 | };
52 | },
53 | );
54 | }
55 | }
56 | ```
57 |
58 | ## Authorizing your functions
59 |
60 | Once a user is signed in, they can access functions marked `@authenticated`. To learn more about authorizing your functions, check out [Authorizing functions](/docs/functions/authorizing-functions).
61 |
--------------------------------------------------------------------------------
/pages/docs/cli-commands-reference.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 4
3 | description: Learn more about Celest and how it helps you build the backend for your Flutter app.
4 | ---
5 |
6 | # CLI commands reference
7 |
8 | The following is a list of all the available commands from the Celest CLI. Visit our [downloads](/#download) page
9 | to install the Celest CLI on your computer.
10 |
11 | | Command | Description |
12 | | --------- | ------------------------------------------------------------- |
13 | | `celest init`| Creates a new Celest project |
14 | | `celest start`| Starts a local development environment |
15 | | `celest deploy` | Deploys your project to Celest Cloud |
16 | | `celest build` | Compiles and bundles your project for self-hosting |
17 | | `celest upgrade` | Upgrades the Celest CLI to the latest version. |
18 | | `celest uninstall`| Uninstall the Celest CLI and remove all associated data. |
19 | | `celest --help`| Show all available commands for the Celest CLI. |
20 | | `celest --version`| Show the current version of the Celest CLI. |
21 |
--------------------------------------------------------------------------------
/pages/docs/data.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Data
3 | description: Learn how to add a database to your Celest project.
4 | ---
5 |
6 | # Data
7 |
8 | Celest offers built-in database support thanks to the powerful combination of the [Drift](https://pub.dev/packages/drift) library
9 | and the [Turso](https://turso.tech/) platform.
10 |
11 | Drift is a Dart-native ORM that allows you to define your database schema in Dart code and generate the necessary
12 | SQL code to create and manage your database. Drift works best when it's backed by SQLite, and we think SQLite is a
13 | great choice of database for both frontend and backend environments alike.
14 |
15 | The only problem is that SQLite cannot scale beyond a single server which is why it's often scoffed at by enterprise developers.
16 | That's where Turso comes in. Turso is the company behind [libSQL](https://github.com/tursodatabase/libsql), an open-source fork of SQLite which builds on top
17 | of the SQLite codebase to provide a distributed, scalable, and secure database solution.
18 |
19 | Celest is proud to be the first Dart platform to offer Turso support out of the box, allowing you to build scalable and secure
20 | applications no matter the size of your user base.
21 |
22 | ## Getting Started
23 |
24 | To add a database to your Celest project, you need to define your database schema in Dart code. This is done using the Drift library.
25 |
26 | Here is an example database schema for a simple task management app:
27 |
28 | ```dart filename="celest/lib/src/database/task_database.dart"
29 | import 'package:drift/drift.dart';
30 |
31 | part 'task_database.g.dart';
32 |
33 | class Tasks extends Table {
34 | late final id = integer().autoIncrement()();
35 | late final title = text().withLength(min: 1, max: 100)();
36 | late final completed = boolean().withDefault(const Constant(false))();
37 | }
38 |
39 | @DriftDatabase(tables: [Tasks])
40 | class TaskDatabase extends _$TaskDatabase {
41 | TaskDatabase(super.e);
42 |
43 | @override
44 | int get schemaVersion => 1;
45 | }
46 | ```
47 |
48 | > For more guidance on writing Drift schemas, see the excellent documentation provided by the Drift author on their website: [Drift Documentation](https://drift.simonbinder.eu/docs/).
49 |
50 | Once you have a Drift schema, you're ready to add it to your Celest project. Navigate to your `project.dart` file and add the following code:
51 |
52 | ```dart {6-8} filename="celest/lib/src/project.dart"
53 | import 'package:celest/celest.dart';
54 | import 'package:celest_backend/src/database/task_database.dart';
55 |
56 | const project = Project(name: 'tasks');
57 |
58 | const taskDatabase = Database(
59 | schema: Schema.drift(TaskDatabase),
60 | );
61 | ```
62 |
63 | That's it! When you run `celest start`, Celest will automatically handle the database setup for you.
64 | You can now interact with your database using the generated `celest` global from your cloud functions.
65 |
66 | ```dart filename="celest/lib/src/functions/tasks.dart"
67 | import 'package:celest/celest.dart';
68 | import 'package:celest_backend/src/database/task_database.dart';
69 | import 'package:celest_backend/src/generated/cloud.celest.dart';
70 |
71 | TaskDatabase get db => celest.data.taskDatabase;
72 |
73 | @cloud
74 | Future> listAllTasks() async {
75 | print('Fetching tasks...');
76 | return db.select(db.tasks).get();
77 | }
78 | ```
79 |
--------------------------------------------------------------------------------
/pages/docs/download.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Download
3 | description: Download the Celest CLI and get started building your backend in Dart today.
4 | ---
5 |
6 | import { Callout } from 'nextra/components'
7 | import DownloadButton from '@components/DownloadButton'
8 |
9 | # Download Celest
10 |
11 | You can download the Celest CLI by running the following command:
12 |
13 |
14 |
15 |
16 |
17 | This will install the Celest CLI globally on your system, allowing you to use it from any directory.
18 |
19 |
20 | To ensure that the Celest CLI is available in your PATH, you may need to add the Dart SDK's pub cache to your PATH.
21 |
22 | On macOS and Linux, you can do this by adding the following line to your shell configuration file (e.g., `~/.bashrc`, `~/.zshrc`):
23 |
24 | ```bash
25 | export PATH="$PATH":"$HOME/.pub-cache/bin"
26 | ```
27 |
28 | On Windows, you can add the following location to your PATH environment variable:
29 |
30 | ```
31 | %LOCALAPPDATA%\Pub\Cache\bin
32 | ```
33 |
34 |
--------------------------------------------------------------------------------
/pages/docs/folder-structure.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Folder structure
3 | description: The standard folder structure for defining the different parts of your Celest backend.
4 | ---
5 |
6 | # Folder structure
7 |
8 | Every Celest project uses a standard folder structure for defining the different parts of your backend.
9 |
10 | ```shell
11 | my_project/
12 | └── celest/
13 | ├── pubspec.yaml # (1) Dependencies for your backend
14 | │
15 | ├── client/ # (2) Generated Dart client
16 | │ ├── pubspec.yaml
17 | │ └── my_project_client.dart
18 | │
19 | └── lib/src/ # (3) Your backend code
20 | │── project.dart # (4) Project configuration
21 | └── functions/ # (5) Cloud functions
22 | └── orders_api.dart
23 | └── shipping_api.dart
24 | ```
25 |
26 | The folder structure should look very familiar. The `celest/` directory is just a Dart package, and the `lib/src/` directory is
27 | where you will define all of your backend logic. Of note:
28 |
29 | - (1) The `pubspec.yaml` file in the `celest/` directory is where you define your dependencies for your backend.
30 | These dependencies are separate from both your generated client's dependencies and your Flutter project's dependencies.
31 |
32 | - (2) The `client/` directory is where the Dart client is generated. This client is used to interact with your backend from your
33 | Dart or Flutter app. As the client is generated by the CLI, you should not modify it directly.
34 |
35 | - (3) The `lib/src/` directory is where you define your backend logic. This is where you will define your cloud functions,
36 | data models, and any other backend logic.
37 |
38 | - (4) The `project.dart` file is where you define your project configuration. This file is where you define your project name,
39 | and any other project-wide configuration. Any project-level configuration must be placed in this file.
40 |
41 | - (5) The `functions/` directory is where you define your cloud functions. Each file in this directory groups and organizes multiple
42 | Celest Functions of similar functionality into a namespace called an API.
43 |
44 | All of your Cloud Functions must be defined in the `lib/src/functions` folder for Celest to recognize them.
45 |
46 | Outside of the `project.dart` and `functions/` directories, you can create any other directories and files you need to organize your
47 | backend logic however you like.
48 |
--------------------------------------------------------------------------------
/pages/docs/functions.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Celest Functions
3 | description: Learn about use cases for Celest Functions.
4 | ---
5 |
6 | # Celest Functions
7 |
8 | Celest Functions are the primary Cloud Widget that enable you to run custom business logic in your backend. They run as serverless functions in the cloud, and are triggered by HTTP requests.
9 |
10 | ## Why would I want to use Celest Functions?
11 |
12 | Celest Functions enable you to:
13 | - Integrate your existing tools or data sources and serve that information to your app.
14 | - Create a separation layer between your business logic, and the UI of your app.
15 | - Add features to your backend without needing a new version deployed to the app stores.
16 |
17 | ## Next steps
18 |
19 | Follow our next guide to start building your first Celest Function!
20 |
--------------------------------------------------------------------------------
/pages/docs/functions/_meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "--building": {
3 | "title": "Building",
4 | "type": "separator"
5 | },
6 | "creating-functions": "Creating a function",
7 | "data-types": "Defining your data types",
8 | "exceptions": "Throwing exceptions",
9 | "env-variables": "Configuring environment variables and secrets",
10 | "authorizing-functions": "Authorizing callers",
11 | "--iterating": {
12 | "title": "Iterating",
13 | "type": "separator"
14 | },
15 | "logging": "Logging and debugging",
16 | "testing": "Testing your functions",
17 | "packages": "Using packages",
18 | "--advanced": {
19 | "title": "Advanced",
20 | "type": "separator"
21 | },
22 | "customizing-serialization": "Customizing serialization",
23 | "http": {
24 | "display": "hidden"
25 | }
26 | }
--------------------------------------------------------------------------------
/pages/docs/functions/authorizing-functions.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Authorizing your functions
3 | description: Learn how to establish access control patterns for your Celest Functions.
4 | ---
5 |
6 | # Authorizing your functions
7 |
8 | By default, Celest Functions are deny-by-default, meaning only you as the author and administrator of your project can access them.
9 |
10 | To change the access level, you can use one of the following annotations:
11 |
12 | - `@public` — Grants access to anyone on the Internet, making your function publicly accessible.
13 | - `@authenticated` — Grants access to only authenticated users. (Requires an [auth provider](/docs/auth))
14 |
15 | When using Celest Auth, you have the ability to restrict access to your functions to only authenticated users. That is, those users
16 | who have signed up and are logged in.
17 |
18 | This is done by adding the `@authenticated` annotation to your function. When someone calls this function, either via the Celest client
19 | or directly via HTTP, Celest will check if the user is authenticated before allowing the function to run.
20 |
21 | ```dart {4} filename="celest/lib/src/functions/greeting.dart"
22 | import 'package:celest/celest.dart';
23 |
24 | @cloud
25 | @authenticated
26 | Future sayHello() async {
27 | return 'Hello, valued customer!';
28 | }
29 | ```
30 |
31 | ### Public functions
32 |
33 | If you want to make a function public, you can use the `@public` annotation. This will all authorization middleware from the function
34 | and allow anyone on the Internet to call it.
35 |
36 | ```dart {4} filename="celest/lib/src/functions/greeting.dart"
37 | import 'package:celest/celest.dart';
38 |
39 | @cloud
40 | @public
41 | Future sayHello() async {
42 | return 'Hello, world!';
43 | }
44 | ```
45 |
46 | ## Accessing user information
47 |
48 | To get the calling user's information, you can use the `@principal` annotation in your function and Celest will inject the caller's
49 | information automatically for each request.
50 |
51 | ```dart {6} filename="celest/lib/src/functions/greeting.dart"
52 | import 'package:celest/celest.dart';
53 |
54 | @cloud
55 | @authenticated
56 | Future sayHello({
57 | @principal required User user,
58 | }) async {
59 | return 'Hello, ${user.displayName}!';
60 | }
61 | ```
62 |
63 | You can do this for `@public` functions as well since, in that case, there _may_ be a user that Celest knows about.
64 | For `@public` functions, the injected `User` parameter must be nullable to account for both scenarios.
65 |
66 | ```dart {6-7} filename="celest/lib/src/functions/greeting.dart"
67 | import 'package:celest/celest.dart';
68 |
69 | @cloud
70 | @public
71 | Future sayHello({
72 | // User must be nullable here for `@public` functions
73 | @principal User? user,
74 | }) async {
75 | if (user == null) {
76 | return 'Hello, stranger!';
77 | }
78 | return 'Hello, ${user.displayName}!';
79 | }
80 | ```
--------------------------------------------------------------------------------
/pages/docs/functions/creating-functions.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Creating a function
3 | description: Learn how to create your first Celest cloud function.
4 | ---
5 |
6 | import { Callout } from 'nextra/components'
7 |
8 | # Creating a function
9 |
10 | Create serverless functions with Celest to connect different parts of your backend, and run custom business logic entirely in the cloud.
11 | You define cloud functions as regular Dart functions and Celest will automatically configure the HTTP endpoints, manage the serialization
12 | of Dart types to and from JSON and, when you're ready, deploy your functions to the cloud.
13 |
14 | ## Writing Celest Functions
15 |
16 | To write your first serverless cloud function, navigate to the `my_celest_app/celest/lib/src/functions/` folder and create a file named `.dart`. You can create as many APIs as you want in this directory.
17 | Each file groups and organizes multiple Celest Functions of similar functionality into a namespace.
18 |
19 | Celest Functions are defined as top-level functions as shown below.
20 |
21 |
22 | Only functions annotated with `@cloud` are exposed as cloud functions. This prevents accidental deployment of functions and allows you to have shared logic and helper functions that are not exposed as cloud functions.
23 |
24 |
25 | ```dart
26 | @cloud
27 | Future sayHello(String name) async {
28 | return 'Hello, $name';
29 | }
30 |
31 | @cloud
32 | Future sayGoodbye(String name) async {
33 | return 'Goodbye, $name';
34 | }
35 | ```
36 |
37 | That's all you need to define your API! Now, you can connect your Flutter app to your cloud functions by using the Celest client as shown in the following example. Replace the contents of the `main.dart` file in your Flutter app to the following code-snippet.
38 |
39 | ```dart {3,7,24}
40 | import 'package:flutter/material.dart';
41 | // Import the generated Celest client
42 | import 'package:my_project_client/my_project_client.dart';
43 |
44 | void main() {
45 | // Initialize Celest at the start of your app
46 | celest.init(environment: CelestEnvironment.local);
47 | runApp(const MyApp());
48 | }
49 |
50 | class MyApp extends StatelessWidget {
51 | const MyApp({super.key});
52 |
53 | @override
54 | Widget build(BuildContext context) {
55 | return MaterialApp(
56 | home: Scaffold(
57 | appBar: AppBar(
58 | title: const Text('Homepage'),
59 | ),
60 | body: Center(
61 | child: FutureBuilder(
62 | // Call your function using the Celest client
63 | future: celest.functions.greeting.sayHello('Celest'),
64 | builder: (_, snapshot) => switch (snapshot) {
65 | AsyncSnapshot(:final data?) => Text(data),
66 | AsyncSnapshot(:final error?) =>
67 | Text('${error.runtimeType}: $error'),
68 | _ => const CircularProgressIndicator(),
69 | },
70 | ),
71 | ),
72 | ),
73 | );
74 | }
75 | }
76 | ```
77 |
78 | ## Next steps
79 |
80 | You now know how to create Celest Functions and connect them to your Flutter app. We have other guides to help explain how to use features such as [logging](/docs/functions/logging.md), [using custom data types](/docs/functions/data-types.md), and [managing environment variables](/docs/functions/env-variables.md).
81 |
--------------------------------------------------------------------------------
/pages/docs/functions/customizing-serialization.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Customizing serialization
3 | description: Learn how to customize serialization logic for your Celest Functions.
4 | ---
5 |
6 | import { Callout } from 'nextra/components'
7 |
8 | # Customizing serialization
9 |
10 | If you need custom handling for your serialization logic, either to conform to an existing interface or to ensure parity with other parts
11 | of your application, define the `fromJson` and `toJson` methods to your data type. Celest will use these methods when provided instead of
12 | its default serialization logic.
13 |
14 | ## Example
15 |
16 | Consider the `Price` type that we defined in [Defining your data types](./data-types.mdx).
17 |
18 | ```dart
19 | enum Currency { usd, cad, ... }
20 |
21 | class Price {
22 | const Price({
23 | required this.currency,
24 | required this.dollars,
25 | required this.cents,
26 | }): assert(cents < 100);
27 |
28 | final Currency currency;
29 | final int dollars;
30 | final int cents;
31 | }
32 | ```
33 |
34 | By default, Celest follows the conventions of `package:json_serializable` and will serialize the `Price` type as:
35 |
36 | ```json
37 | {
38 | "currency": "usd",
39 | "dollars": 100,
40 | "cents": 34
41 | }
42 | ```
43 |
44 | If, however, you want to serialize the `currency` field as uppercase, you can define the `fromJson` and `toJson` methods
45 | in the `Price` class.
46 |
47 | ```dart {6-8,15} filename="celest/lib/src/models/price.dart"
48 | class Price {
49 | // ...
50 |
51 | factory Price.fromJson(Map json) {
52 | return Price(
53 | currency: Currency.values.firstWhere(
54 | (e) => e.toString().toUpperCase() == json['currency'],
55 | ),
56 | dollars: json['dollars'] as int,
57 | cents: json['cents'] as int,
58 | );
59 | }
60 |
61 | Map toJson() => {
62 | 'currency': currency.name.toUpperCase(),
63 | 'dollars': dollars,
64 | 'cents': cents,
65 | };
66 | }
67 | ```
68 |
69 | Then, anytime you use the `Price` type in a cloud function, Celest will use this logic for handling
70 | the serialization. And the resulting JSON response for the `currency` will be properly upper-cased.
71 |
72 | ```diff {6,7}
73 | {
74 | "customerOrder": {
75 | "id": 123,
76 | "customerName": "Celest",
77 | "price": {
78 | + "currency": "USD",
79 | - "currency": "usd",
80 | "dollars": 100,
81 | "cents": 34
82 | }
83 | }
84 | }
85 | ```
86 |
87 | ## Customize serialization of third-party types [#custom-overrides]
88 |
89 | Sometimes, though, you cannot control the `fromJson`/`toJson` methods of a class, such as when using a third-party library.
90 | In these cases, you can create a `@customOverride` which "redefines" the type for serialization in Celest.
91 |
92 | For example, consider the case where the `Price` class from our `Order` type is imported from a third-party library, `package:price`.
93 | Since we do not own `package:price`, we cannot change the `fromJson` and `toJson` methods of the `Price` class.
94 |
95 | Instead of modifying the `Price` class, we can define a custom override for `Price` which will be used in place of the original
96 | `Price` class when it's encountered in Celest.
97 |
98 |
99 | Custom overrides are defined as [extension types](https://dart.dev/language/extension-types) which wrap and implement the original type.
100 | You can use these to override any part of the original interface to suit its use in your backend, though customizing serialization is the
101 | most common use case.
102 |
103 | You must always **implement** the original type in your extension type and mark it with `@customOverride`. By convention, these types are
104 | defined in either `lib/models/_overrides.dart` or `lib/exceptions/_overrides.dart` and are not used directly in your backend. They serve as
105 | global marker types which Celest will use internally.
106 |
107 |
108 | ```dart {10-11} filename="celest/lib/src/models/_overrides.dart"
109 | import 'package:celest/celest.dart';
110 | import 'package:price/price.dart';
111 |
112 | // We create an extension type which wraps over the third-party type and
113 | // is marked as a custom override of it.
114 | //
115 | // You do not need to use this type directly in your backend. Celest will
116 | // use it internally anytime it encounters the original type.
117 | @customOverride
118 | extension type PriceOverride(Price price) implements Price {
119 | factory PriceOverride.fromJson(Map json) {
120 | return PriceOverride(
121 | Price(
122 | currency: Currency.values.firstWhere(
123 | (e) => e.toString().toUpperCase() == json['currency'],
124 | ),
125 | dollars: json['dollars'] as int,
126 | cents: json['cents'] as int,
127 | ),
128 | );
129 | }
130 |
131 | Map toJson() => {
132 | 'currency': price.currency.toString().toUpperCase(),
133 | 'dollars': price.dollars,
134 | 'cents': price.cents,
135 | };
136 | }
137 | ```
138 |
139 | ## Next steps
140 |
141 | You have now learned how Celest handles the serialization of requests/responses to your functions, and how to use your own custom types and serialization logic. Learn about more features of Celest Functions by following our guides for [defining custom exceptions](/docs/functions/exceptions.md) and [managing environment variables](/docs/functions/env-variables.md).
142 |
--------------------------------------------------------------------------------
/pages/docs/functions/data-types.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Defining your data types
3 | description: Learn how to define data types which can be passed to and from your Celest Functions.
4 | ---
5 |
6 | import { Callout } from 'nextra/components'
7 |
8 | # Defining your data types
9 |
10 | With Celest Functions, you can use any of the core Dart types available such as `int`, `String`, and `DateTime`.
11 | You can also use your own data types. Celest will automatically handle the transfer of data to and from your Flutter app
12 | (a process known as serialization) so you don't need to think about it.
13 |
14 | > If you'd prefer to handle the serialization yourself, Celest will use your [custom logic](./customizing-serialization.mdx) instead.
15 |
16 | Imagine you're working on an e-commerce application with an `Order` class defined in your codebase.
17 |
18 | ```dart filename="celest/lib/src/models/order.dart"
19 | class Order {
20 | const Order ({
21 | required this.id,
22 | required this.customerName,
23 | required this.price,
24 | });
25 |
26 | final int id;
27 | final String customerName;
28 | final Price price;
29 | }
30 |
31 | enum Currency { usd, cad, ... }
32 |
33 | class Price {
34 | const Price({
35 | required this.currency,
36 | required this.dollars,
37 | required this.cents,
38 | }): assert(cents < 100);
39 |
40 | final Currency currency;
41 | final int dollars;
42 | final int cents;
43 | }
44 | ```
45 |
46 | When you pass this `Order` type as a parameter to a cloud function or when you return it from a cloud function,
47 | Celest will automatically serialize and deserialize it for you.
48 |
49 | ```dart {1,5} filename="celest/lib/src/functions/orders.dart"
50 | import 'package:celest_backend/models/order.dart';
51 |
52 | @cloud
53 | Future createOrder(
54 | Order customerOrder,
55 | ) async {
56 | // Function logic goes here
57 | }
58 | ```
59 |
60 | Celest does this by inspecting every function marked with `@cloud` to find the data types you've used and any types those types use
61 | and generates a schema for each of them. This schema is then used to serialize and deserialize the data to and from JSON.
62 |
63 | For example when passing an order to your backend, Celest will serialize the `Order` class as a JSON map with the field names as keys and
64 | the values marshalled to the appropriate JSON representation.
65 |
66 | ```json
67 | {
68 | "customerOrder": {
69 | "id": 123,
70 | "customerName": "Celest",
71 | "price": {
72 | "currency": "usd",
73 | "dollars": 100,
74 | "cents": 34
75 | }
76 | }
77 | }
78 | ```
79 |
80 | Similarly if your Flutter app receives an `Order` object from a Celest Function, Celest knows how to take this JSON and map it back into
81 | the same Dart type. The process happens so transparently that you'll feel like you're just passing Dart objects directly between two
82 | local functions.
83 |
84 | ## Next steps
85 |
86 | Read on to see how you can seamlessly catch errors thrown from your backend in your Flutter app and how to configure the environment
87 | in which your backend runs.
88 |
--------------------------------------------------------------------------------
/pages/docs/functions/env-variables.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Configuring environment variables and secrets
3 | description: Learn how to create and manage environment variables and secrets that your Celest Functions can access.
4 | ---
5 |
6 | import { Callout } from 'nextra/components'
7 |
8 | # Configuring environment variables and secrets
9 |
10 | Both environment variables and secrets can be used to provide your Celest backend with environment-specific configuration.
11 | They allow you to keep configuration values separate from your codebase, improving flexibility when deploying your backend
12 | to different environments and isolating sensitive information from your frontend.
13 |
14 | Because of this, it's important to keep in mind that environment variables and secrets are not accessible from your frontend code.
15 |
16 | ## Creating environment variables
17 |
18 | To declare an environment variable, add a Dart variable in your `project.dart` file using the `env` constructor.
19 |
20 | ```dart filename="celest/lib/src/project.dart"
21 | import 'package:celest/celest.dart';
22 |
23 | const project = Project(name: 'my-project');
24 |
25 | const myEnv = env('MY_ENV');
26 | ```
27 |
28 | The name passed to `env` is the name as it appears in the platform environment. This name will be used to configure your Celest Functions when
29 | they're deployed to Celest Cloud or when self hosting in a container.
30 |
31 | Celest keeps track of the unique variables throughout your project and ensures that any missing values are provided before you deploy your project.
32 | When you run `celest start` for the first time, it will prompt you for the value of each environment variable. After you provide the values, Celest
33 | will securely store them for future use.
34 |
35 | ### Using `.env` files
36 |
37 | You can also use a `.env` file to store your environment variables. This file should be placed in the root of your project directory in the `celest/`
38 | folder. When a `.env` file is present, Celest will start by reading the values from the file and only then prompt you for any missing values.
39 |
40 | When the values of these variables changes between environments, you can suffix your `.env` files with the name of the environment they apply to.
41 | For example, Celest currently provides a `local` and `production` environment. Given the following `.env` files:
42 |
43 | ```plaintext
44 | my-project/
45 | celest/
46 | .env.local
47 | .env.production
48 | pubspec.yaml
49 | ```
50 |
51 | ```plaintext filename=".env.local"
52 | MY_ENV=local-value
53 | ```
54 |
55 | ```plaintext filename=".env.production"
56 | MY_ENV=production-value
57 | ```
58 |
59 | The value of `MY_ENV` will be `local-value` when you run `celest start`, and `production-value` you run `celest deploy` or `celest build`.
60 |
61 | ## Using environment variables with Celest Functions
62 |
63 | To get the value of an environment variable in a Celest Function, you can use the generated `celest` global. Just like in your Flutter app,
64 | Celest will generated some helper methods to access important parts of your project.
65 |
66 | In this case, we can use `celest.variables.myEnv` to access the value of the `MY_ENV` environment variable.
67 |
68 | ```dart {6} filename="celest/lib/src/functions/greeting.dart"
69 | // Import the generated `celest` global
70 | import 'package:celest_backend/src/generated/cloud.celest.dart';
71 |
72 | @cloud
73 | Future sayHello(String name) async {
74 | print('My environment variable is ${celest.variables.myEnv}');
75 | return 'Hello, $name!';
76 | }
77 | ```
78 |
79 | When the `sayHello` function runs, it will automatically have access to the environment variable. Its value is stored securely and is never
80 | hard-coded in your codebase or in the deployed Celest Function.
81 |
82 | ## Creating secrets
83 |
84 | Secrets are created and managed exactly like environment variables. The only difference is that secrets are encrypted by Celest and are not
85 | displayed in the console when you run `celest start`. Otherwise, how you use them in your code is the same as environment variables.
86 |
87 | To create a secret, use the `secret` constructor instead of `env`.
88 |
89 | ```dart {6} filename="celest/lib/src/project.dart"
90 | import 'package:celest/celest.dart';
91 |
92 | const project = Project(name: 'my-project');
93 |
94 | const myEnv = env('MY_ENV');
95 | const mySecret = secret('MY_SECRET');
96 | ```
97 |
98 | Then, you can use the `celest.secrets` helper in your functions in the same way as environment variables.
99 |
100 | ```dart {7} filename="celest/lib/src/functions/greeting.dart"
101 | // Import the generated `celest` global
102 | import 'package:celest_backend/src/generated/cloud.celest.dart';
103 |
104 | @cloud
105 | Future sayHello(String name) async {
106 | print('My environment variable is ${celest.variables.myEnv}');
107 | print('My secret is ${celest.secrets.mySecret}');
108 | return 'Hello, $name!';
109 | }
110 | ```
111 |
112 | ## Next steps
113 |
114 | You have now learned about the flows available for updating, retreiving, and deleting environment variables in your Celest project. You can follow additional guides to learn more about [testing your functions](/docs/functions/testing.md), and [installing 3rd party Dart packages](/docs/functions/packages.md).
115 |
--------------------------------------------------------------------------------
/pages/docs/functions/exceptions.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Throwing exceptions
3 | description: Learn how Celest handles thrown exceptions on your backend and how to catch them in your Flutter app.
4 | ---
5 |
6 | import { Callout } from 'nextra/components'
7 |
8 | # Throwing exceptions
9 |
10 | Celest also knows how to handle exceptions thrown in your Celest Functions. When an exception is thrown, Celest will do three things:
11 |
12 | - Catch the thrown exception serialize it using the same process outlined above
13 | - Map the exception to an appropriate HTTP response and status code
14 | - Deserialize the exception type in your Flutter app and rethrow the same Dart type with the original message
15 |
16 | Suppose you have some validation logic on your server which checks that a user's name is properly formatted. If the name is empty,
17 | you want to throw an exception. You can define a Dart type in your Celest backend to represent this error.
18 |
19 | ```dart filename="celest/lib/src/exceptions/bad_name_exception.dart"
20 | class BadNameException implements Exception {
21 | const BadNameException(this.message);
22 |
23 | final String message;
24 | }
25 | ```
26 |
27 | When you encounter an empty name, you can throw this exception in your Celest Function.
28 |
29 | ```dart {7} filename="celest/lib/src/functions/greeting.dart"
30 | import 'package:celest_backend/exceptions/bad_name_exception.dart';
31 |
32 | @cloud
33 | Future sayHello(String name) async {
34 | // Perform custom validation
35 | if (name.isEmpty) {
36 | throw const BadNameException('Name cannot be empty');
37 | }
38 | return 'Hello, $name';
39 | }
40 | ```
41 |
42 | In your Flutter app, the same `BadNameException` type will be thrown by the generated client so
43 | you can catch it and handle it as you would any other exception. Any `message` you passed in the
44 | on the server will be available in the `message` field of the exception on the client. Magically!
45 |
46 | ```dart {8-11}
47 | import 'package:my_project_client/my_project_client.dart';
48 |
49 | @cloud
50 | Future getGreeting(String name) async {
51 | try {
52 | return await celest.functions.greeting.sayHello(name);
53 | // Catch the exception type defined in your backend
54 | } on BadNameException catch (e) {
55 | print(e.message); // prints "Name cannot be empty"
56 | rethrow;
57 | }
58 | }
59 | ```
60 |
61 | ## HTTP mapping
62 |
63 | When an exception is thrown in your Celest Function, Celest will map the exception to an appropriate HTTP response code.
64 | For example, all `Exception` types will be mapped to a `400 Bad Request` status and all `Error` types will be mapped
65 | to a `500 Internal Server Error` status by default.
66 |
67 | Celest also provides a number of built-in exception types for common HTTP status codes. For example, you can throw a `NotFoundException`
68 | to return a `404 Not Found` status code. And you can even include a custom message with it.
69 |
70 | ```dart
71 | import 'package:celest/celest.dart';
72 |
73 | @cloud
74 | Future sayHello(String name) async {
75 | if (name.isEmpty) {
76 | throw const NotFoundException('Name cannot be empty');
77 | }
78 | return 'Hello, $name';
79 | }
80 | ```
81 |
82 | If an exception type you've defined needs to be mapped to a different status code, you can use the `@httpError` annotation to
83 | specify the status code Celest should use for it. For example, maybe we want our `BadNameException` to return a `404 Not Found` status
84 | instead of the default `400`.
85 |
86 | ```dart
87 | import 'package:celest/celest.dart';
88 | import 'package:celest/http.dart';
89 |
90 | class BadNameException implements Exception {
91 | const BadNameException(this.message);
92 |
93 | final String message;
94 | }
95 |
96 | @cloud
97 | @httpError(404, BadNameException)
98 | Future sayHello(String name) async {
99 | if (name.isEmpty) {
100 | throw const BadNameException('Name cannot be empty');
101 | }
102 | return 'Hello, $name';
103 | }
104 | ```
105 |
106 | ## Next steps
107 |
108 | You have now learned how to handle exceptions in your Celest Functions. Keep reading to learn about [configuring your environment](./env-variables.mdx)
109 | and [ensuring your backend is secure](./authorizing-functions.mdx).
110 |
--------------------------------------------------------------------------------
/pages/docs/functions/http.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Standard HTTP conventions
3 | description: Learn how your Celest Functions can be called using normal HTTP requests.
4 | ---
5 |
6 | # Using HTTP calls
7 |
8 | If you'd like to use your Celest Functions outside of your Flutter 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.
9 |
10 | ## Celest's HTTP conventions
11 |
12 | When communicating with Celest Functions using HTTP calls, all requests need to be sent as `POST` calls. Additionally, Celest handle all request/responses as JSON objects.
13 |
14 | The following is a list of the HTTP response code conventions used by Celest.
15 |
16 |
17 | | Response Code | Description |
18 | | --------- | ------------------------------------------------------------- |
19 | | `200` | Indicates a call was successful |
20 | | `400` | Indicates an Exception was thrown |
21 | | `500` | Indicates an Error was thrown |
22 |
23 | ## Error formats
24 |
25 | When a function fails with an exception or error, the HTTP 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.
26 |
27 | For example, let us assume you have a custom exception called `BadNameException` type defined in the example below.
28 |
29 | ```dart filename="celest/lib/src/exceptions/bad_name_exception.dart"
30 | class BadNameException implements Exception {
31 | const BadNameException(this.message);
32 |
33 | final String message;
34 | }
35 | ```
36 |
37 |
38 | If this exception is returned in one of your Celest Functions that you are calling with an HTTP request, the resulting JSON response will be as shown below.
39 |
40 | ```http
41 | HTTP/1.1 400 Bad Request
42 | Content-Type: application/json
43 |
44 | {
45 | "error": {
46 | "code": "BadNameException",
47 | "details": {
48 | "message": "Input cannot be empty"
49 | }
50 | }
51 | }
52 | ```
53 |
54 | However, if the function threw a `StateError`, it would look like this. For non-custom exceptions, the "details" field is omitted. This is done in order to prevent leaking information which may be sensitive to your application. Define custom exceptions to provide more information to your users as appropriate.
55 |
56 | ```http
57 | HTTP/1.1 500 Internal Server Error
58 | Content-Type: application/json
59 |
60 | {
61 | "error": {
62 | "code": "StateError"
63 | }
64 | }
65 | ```
66 |
67 | ## Next steps
68 |
69 | You have now learned about the conventions that Celest uses when passing data between your backend and your Flutter application, in addition to the format of the request/response and the HTTP response codes supported. With this knowledge, you now know how to connect to your Celest Functions directly using HTTP calls if you are not using a Flutter app.
70 |
--------------------------------------------------------------------------------
/pages/docs/functions/http/_meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "conventions": "Calling convention",
3 | "customization": "Customization"
4 | }
--------------------------------------------------------------------------------
/pages/docs/functions/http/conventions.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Standard HTTP conventions
3 | description: Learn how your Celest Functions can be called using normal HTTP requests.
4 | ---
5 |
6 | # Using HTTP calls
7 |
8 | If you'd like to use your Celest Functions outside of your Flutter 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.
9 |
10 | ## Celest's HTTP conventions
11 |
12 | When communicating with Celest Functions using HTTP calls, all requests need to be sent as `POST` calls. Additionally, Celest handle all request/responses as JSON objects.
13 |
14 | The following is a list of the HTTP response code conventions used by Celest.
15 |
16 |
17 | | Response Code | Description |
18 | | --------- | ------------------------------------------------------------- |
19 | | `200` | Indicates a call was successful |
20 | | `400` | Indicates an Exception was thrown |
21 | | `500` | Indicates an Error was thrown |
22 |
23 | ## Error formats
24 |
25 | When a function fails with an exception or error, the HTTP 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.
26 |
27 | For example, let us assume you have a custom exception called `BadNameException` type defined in the example below.
28 |
29 | ```dart
30 | // celest/lib/exceptions/bad_name_exception.dart
31 |
32 | class BadNameException implements Exception {
33 | const BadNameException(this.message);
34 |
35 | final String message;
36 | }
37 | ```
38 |
39 |
40 | If this exception is returned in one of your Celest Functions that you are calling with an HTTP request, the resulting JSON response will be as shown below.
41 |
42 | ```http
43 | HTTP/1.1 400 Bad Request
44 | Content-Type: application/json
45 |
46 | {
47 | "error": {
48 | "code": "BadNameException",
49 | "details": {
50 | "message": "Input cannot be empty"
51 | }
52 | }
53 | }
54 | ```
55 |
56 | However, if the function threw a `StateError`, it would look like this. For non-custom exceptions, the "details" field is omitted. This is done in order to prevent leaking information which may be sensitive to your application. Define custom exceptions to provide more information to your users as appropriate.
57 |
58 | ```http
59 | HTTP/1.1 500 Internal Server Error
60 | Content-Type: application/json
61 |
62 | {
63 | "error": {
64 | "code": "StateError"
65 | }
66 | }
67 | ```
68 |
69 | ## Next steps
70 |
71 | You have now learned about the conventions that Celest uses when passing data between your backend and your Flutter application, in addition to the format of the request/response and the HTTP response codes supported. With this knowledge, you now know how to connect to your Celest Functions directly using HTTP calls if you are not using a Flutter app.
72 |
--------------------------------------------------------------------------------
/pages/docs/functions/http/customization.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: HTTP Customization
3 | description: Learn how to control and customize the HTTP API generated for your Celest Functions.
4 | ---
5 |
6 | import { Callout } from 'nextra-theme-docs'
7 |
8 | # HTTP Customization
9 |
10 | In cases where Celest's standard HTTP conventions do not align with your requirements, you can control the HTTP API generated for your Celest Functions using the HTTP annotations.
11 |
12 |
13 | Some HTTP elements are not currently configurable, such as the URL path. If you require this functionality, please let us know by creating an [issue](https://github.com/celest-dev/celest/issues/new/choose).
14 |
15 |
16 | ## Execution Controls
17 |
18 | The `@http` and `@httpError` annotations allow you to control the behavior of the HTTP endpoint, such as how the endpoint is exposed,
19 | and how it responds in both success and error cases.
20 |
21 | ### `@http`
22 |
23 | To configure the method and default status code of your HTTP endpoint, use the `@http` annotation.
24 |
25 | ```dart
26 | @cloud
27 | @http(
28 | method: HttpMethod.put,
29 | statusCode: HttpStatus.created,
30 | )
31 | Future greet(String name) async {
32 | return 'Hello, $name';
33 | }
34 | ```
35 |
36 |
58 |
59 | ### `@httpError`
60 |
61 | To control the status code returned from one or more thrown types, use the `@httpError` annotation.
62 |
63 | ```dart
64 | @cloud
65 | @httpError(403, UnauthorizedException)
66 | @httpError(404, BadNameException, NotFoundException)
67 | Future greet(String name) async {
68 | if (name.isEmpty) {
69 | throw BadNameException();
70 | }
71 | if (!name.startsWith('C')) {
72 | throw NotFoundException();
73 | }
74 | if (name != 'Celest') {
75 | throw UnauthorizedException();
76 | }
77 | return 'Hello, $name';
78 | }
79 | ```
80 |
81 |
82 | Exception types are handled in order of specificity. Specifying a type that other types inherit from will only apply the status code
83 | to subtypes which are not already covered by a more specific case.
84 |
85 | For example, the following will return a 404 status code for both the concrete `NotFoundException` type and `BadNameException` (since it
86 | is a subtype of `Exception`), but not `UnauthorizedException` which is covered by a more specific annotation.
87 |
88 | ```dart
89 | @cloud
90 | @httpError(404, NotFoundException, Exception)
91 | @httpError(403, UnauthorizedException)
92 | Future greet(String name) async {
93 | if (name.isEmpty) {
94 | throw BadNameException(); // 404
95 | }
96 | if (!name.startsWith('C')) {
97 | throw NotFoundException(); // 404
98 | }
99 | if (name != 'Celest') {
100 | throw UnauthorizedException(); // 403
101 | }
102 | return 'Hello, $name';
103 | }
104 | ```
105 |
106 |
107 | ## Request Controls
108 |
109 | The `@httpHeader` and `@httpQuery` annotations allow you to map input parameters to HTTP headers and query parameters, respectively.
110 | Any parameters which are not targeted by these annotations are passed as the request body.
111 |
112 |
113 | Some combinations are disallowed. For example, if the `@http` annotation specifies a `GET` method, then all input
114 | parameters **must** be mapped, or there must be no input parameters, such that the request body is empty.
115 |
116 |
117 | ### `@httpHeader`
118 |
119 | To map an input parameter to an HTTP header, use the `@httpHeader` annotation.
120 |
121 |
122 | Only parameters of type `String`, `int`, `double`, `bool`, `DateTime`, or a `List` of these types, can be targeted by `@httpHeader`.
123 |
124 |
125 | ```dart
126 | @cloud
127 | Future greet({
128 | @httpHeader('x-api-key') required String apiKey,
129 | required String name,
130 | }) async {
131 | return 'Hello, $name';
132 | }
133 | ```
134 |
135 | In the generated client, these parameters are passed transparently as function arguments.
136 |
137 | ```dart
138 | final result = await celest.functions.greet(apiKey: '123', name: 'Celest');
139 | ```
140 |
141 |
--------------------------------------------------------------------------------
/pages/docs/functions/img/file-middleware.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/celest-dev/website/52b36987c64dafebc23000a75788f5430d28e03f/pages/docs/functions/img/file-middleware.png
--------------------------------------------------------------------------------
/pages/docs/functions/img/function.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/celest-dev/website/52b36987c64dafebc23000a75788f5430d28e03f/pages/docs/functions/img/function.png
--------------------------------------------------------------------------------
/pages/docs/functions/img/individual-middleware.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/celest-dev/website/52b36987c64dafebc23000a75788f5430d28e03f/pages/docs/functions/img/individual-middleware.png
--------------------------------------------------------------------------------
/pages/docs/functions/logging.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Logging and debugging
3 | description: Learn how to use logging for debugging your Celest Functions.
4 | ---
5 |
6 | # Logging and debugging
7 |
8 | Celest enables you to use logging to capture helpful information about your functions while they execute. Within your function code, use `print()` statements or a custom logger package which prints to the conosole. These logs will appear in the same console where the `celest start` command is running.
9 |
10 | ## Adding logs to your functions
11 |
12 | Here is an example of using print statements to log the input of `name` in a Celest Function.
13 |
14 | ```dart {3,9} filename="celest/lib/src/functions/greeting.dart"
15 | @cloud
16 | Future sayHello(String name) async {
17 | print('Saying hello to $name');
18 | return 'Hello, $name';
19 | }
20 |
21 | @cloud
22 | Future sayGoodbye(String name) async {
23 | print('Saying goodbye to $name');
24 | return 'Goodbye, $name';
25 | }
26 | ```
27 |
28 | When you call the function, these logs will be streamed locally and shown in the terminal where the `celest start` command is running.
29 |
30 | ```shell
31 | $ celest start
32 | ✓ Celest is running on http://localhost:7777
33 |
34 | [greeting/sayHello] Saying hello to Celest
35 | [greeting/sayGoodbye] Saying goodbye to Celest
36 | ```
37 |
38 | ## Next steps
39 |
40 | You have now learned how to use Logging to help you understand how your Celest Functions are behaving, or debug specific scenarios or flows. Follow our guides to learn about [testing your functions](/docs/functions/testing.md) and [using 3rd party Dart packages](/docs/functions/packages.md).
41 |
--------------------------------------------------------------------------------
/pages/docs/functions/packages.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Using packages
3 | description: Learn how to integrate existing Dart packages into your Celest backend.
4 | ---
5 |
6 | # Using packages
7 |
8 | Your Celest backend is a pure Dart application. Any Dart packages which can be used in a Dart app can be used in your functions. For example, to communicate with systems outside of Celest, you could use `package:http`, `package:dio`, or any other HTTP client compatible with pure Dart.
9 |
10 | ## Adding a Dart package to your backend
11 |
12 | To add a package for use in your backend, navigate in your terminal to the `/celest` folder and then run the `dart pub add ` command.
13 |
14 | ```shell
15 | $ dart pub add
16 | ```
17 |
18 | ## Next steps
19 |
20 | You have now learned how to install packages in your Celest backend. Keep reading to learn about more advanced topics
21 | like [customizing serialization](./customizing-serialization.mdx) or check out our guides on [Auth](/docs/auth) and [Data](/docs/data).
22 |
23 |
--------------------------------------------------------------------------------
/pages/docs/functions/testing.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Testing your functions
3 | description: Learn how to test your Celest Functions.
4 | ---
5 |
6 | # Testing your functions
7 |
8 | Celest Functions you define are Dart functions and can be tested using any of the available Dart testing packages or frameworks.
9 |
10 | ## Writing your tests
11 |
12 | Within your `celest/test` folder, create a new Dart file and write the unit tests for your functions using `package:test` or any other Dart testing framework. The following code snippet has an example of a test for the `sayHello` function.
13 |
14 | ```dart
15 | import 'package:celest/celest.dart';
16 | import 'package:celest_backend/src//functions/greeting.dart';
17 | import 'package:test/test.dart';
18 |
19 | void main() {
20 | test('sayHello', () async {
21 | final result = await sayHello(
22 | name: 'Celest',
23 | greetingUrl: Uri.parse('http://localhost:8000'),
24 | );
25 | expect(result, 'Hello, Celest');
26 | });
27 | }
28 | ```
29 |
30 | Run your tests by navigating to your `celest/` folder in your console, and then running the following command:
31 |
32 | ```shell
33 | dart test
34 | ```
35 |
36 | Your terminal will let you know if all your tests pass, or if there are any failed tests that you need to review.
37 |
38 | ## Next steps
39 |
40 | You have now learned how to write tests for your Celest Functions. Keep reading to learn how to start
41 | [adding other packages](./packages.mdx) in your Celest backend.
42 |
--------------------------------------------------------------------------------
/pages/docs/get-started.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Getting started
3 | description: Learn how to set up and use Celest in your Flutter project.
4 | ---
5 |
6 | import { Cards, Card } from 'nextra/components';
7 | import { FunctionsIcon, AuthIcon, DataIcon } from '@components/icons';
8 | import DownloadButton from '@components/DownloadButton';
9 |
10 | # Getting started
11 |
12 | This guide walks you through setting up Celest on your development machine, and how to create a new project.
13 |
14 | To get started with Celest, you'll need to configure your development environment so that you have Flutter and the Celest CLI installed on your machine.
15 |
16 | 1. Install Flutter v3.29.0 or later from the [official website](https://docs.flutter.dev/get-started/install)
17 | 2. Download and install the Celest CLI
18 |
19 |
20 |
21 |
22 |
23 | ## Creating a project
24 |
25 | Once you have the CLI installed, you can create a new project by running the following command:
26 |
27 | ```bash
28 | $ celest init
29 | ```
30 |
31 | You can run this command from within your Flutter project which will create a new `celest/` directory for your project. Or you can run
32 | this in another directory to have a standalone Celest project.
33 |
34 | Once you have a project, run `celest start` to start a local development environment.
35 |
36 | ```bash
37 | $ celest start
38 | ✓ Celest is running on http://localhost:7777
39 | ```
40 |
41 | This command will start a local server which will run in the background as you write your backend logic. As you make changes to the files in the `celest/` directory,
42 | the server will hot-reload those changes so you can see them live.
43 |
44 | To interact with the running environment, Celest will generate a Dart client which you can use in any Dart or Flutter project. This client
45 | is generated in the `client/` directory of your `celest/` folder. As you make changes in the local environment, this client will be updated to reflect those changes.
46 |
47 | ### Example
48 |
49 | Here is an example of a simple Celest function:
50 |
51 | ```dart
52 | import 'package:celest/celest.dart';
53 |
54 | @cloud
55 | Future sayHello(String name) async {
56 | print('Saying hello to $name');
57 | return 'Hello, $name';
58 | }
59 | ```
60 |
61 | This function can be called from a Dart project like so:
62 |
63 | ```dart
64 | import 'package:my_project_client/my_project_client.dart';
65 |
66 | Future main() async {
67 | celest.init(environment: CelestEnvironment.local);
68 | final response = await celest.functions.sayHello('World');
69 | print(response); // Hello, World
70 | }
71 | ```
72 |
73 | ## Next steps
74 |
75 | Ready to start building? Choose which part of your backend you want to work on first:
76 |
77 |
84 |
--------------------------------------------------------------------------------
/pages/docs/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: What is Celest?
3 | description: Learn more about Celest and how it helps you build the backend for your Flutter app.
4 | ---
5 |
6 | import { Callout } from 'nextra/components'
7 | import { IntroducingCelest } from '@components/IntroducingCelest'
8 | import { CloudFunctionsDemo } from '@components/CloudFunctionsDemo'
9 | import { Cards, Card } from 'nextra/components'
10 | import { FunctionsIcon, AuthIcon, DataIcon, PoliciesIcon, StorageIcon, WebsiteIcon } from '@components/icons'
11 |
12 | # Overview
13 |
14 | ## What is Celest?
15 |
16 | Celest is a backend-as-a-service for Flutter developers that enables you to build your backend entirely in Dart. Build the use cases you need without any additional tooling or context switching to different programming languages. Celest lets you write your backend in Dart, enabling you to share logic between your Flutter app and your backend.
17 |
18 |
19 |
20 | ## What can I build with Celest?
21 |
22 | Celest offers the following building blocks for your backend:
23 |
24 |
Serve content globally with edge caching built-in.
45 |
46 |
47 | } title="Web (coming soon)" href="#" />
48 |
Integated hosting for Dart and Flutter web apps.
49 |
50 |
51 |
52 |
53 | _Something we're missing that you'd like to see?_ Let us know by [creating an issue](https://github.com/celest-dev/celest/issues/new/choose)
54 | in our GitHub repo or chatting with us on [Discord](/discord).
55 |
56 | ## How do I use Celest?
57 |
58 | You primarily interact with Celest using the [Celest CLI](/docs/download).
59 |
60 | Once you download and install the CLI, run `celest start` from your Flutter project to create the `celest` folder. This folder contains all of your backend logic. The CLI will watch for changes in this folder and hot-reload your backend as you work. A client library is automatically generated for you in your Flutter app. When you feel confident with your changes, use the CLI to [deploy](/docs/deploy) your backend to the cloud.
61 |
62 | Celest offers a set of "Cloud Widgets"—constructs that encapsulate functionality on the backend. Using these widgets you can declaratively define your backend features such as serverless functions and authentication.
63 |
64 |
65 |
66 | ## Next steps
67 |
68 | Follow the next guide to [set up the CLI](/docs/get-started.md) and get started building your backend in Dart.
69 |
--------------------------------------------------------------------------------
/pages/docs/self-hosting.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Self-Hosting
3 | description: Learn how to self-host your Celest project anywhere a Docker container runs.
4 | ---
5 |
6 | import { Callout } from 'nextra/components'
7 |
8 | # Self-Hosting
9 |
10 | As of Celest V1, all Celest projects can be self-hosted on any server that can run a Docker container. This means you can deploy your Celest project
11 | to any cloud provider, on-premises server, or even your own computer.
12 |
13 | Self-hosting support is provided via the `celest build` command which will bundle all of the dependencies of your Celest project into a
14 | lightweight Docker container. The container will be pre-configured with all the necessary environment variables and settings to run your
15 | Celest project.
16 |
--------------------------------------------------------------------------------
/pages/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Celest | The Flutter cloud platform
3 | description: Celest is a Flutter cloud platform that allows you to build, deploy, and manage backends for your Flutter app with ease.
4 | ---
5 |
6 | import { Tabs, Steps } from 'nextra/components'
7 | import { Bleed } from 'nextra-theme-docs'
8 | import Link from 'next/link'
9 | import Image from 'next/image'
10 |
11 | import Hero from '@components/landing/Hero'
12 | import Newsletter from '@components/landing/Newsletter'
13 | import Content from '@components/landing/Content'
14 | import { Download } from '@components/Download'
15 | import { Feature, FeatureSet } from '@components/landing/Feature'
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | Annotate any Dart function with @cloud to turn it into a serverless function.>}
26 | >
27 | ```dart
28 | @cloud
29 | Future sayHello(String name) async {
30 | return 'Hello, $name!';
31 | }
32 | ```
33 |
34 | Out-of-the-box database>}
36 | description="Celest takes your Drift schema and automatically creates a database for you."
37 | >
38 |
39 |
40 |
41 | ```dart filename="database.dart"
42 | import 'package:drift/drift.dart';
43 |
44 | class Tasks extends Table {
45 | late id = integer().autoIncrement()();
46 | late title = text()();
47 | late completed = boolean()();
48 | }
49 | ```
50 |