# Build your first app

https://docs.serverpod.dev/2.4.0/tutorials/first-app

You will build a simple note-taking app in this tutorial. You will learn the fundamental building blocks of Serverpod that enable you to create powerful and scalable server-side applications with ease.

[Watch the video](https://www.youtube-nocookie.com/embed/HW8f8Bn0BB0)

We are assuming you have all the tools setup and ready to go. If not, please follow the [Installing Serverpod](https://docs.serverpod.dev/index.md) guide to get up and running.

We will cover the following topics:

- Creating serializable objects
- Creating a database table
- Creating API endpoints for CRUD operations
- Using the serverpod code generator
- Using the generated client library
- Connecting a Flutter app to the server

Demo of what we will build: ([Full code example](https://github.com/serverpod/notes)).

[Watch the video](https://docs.serverpod.dev/img/tutorial/note-app/01-note-app-demo.mp4)

## Create a new project

Create a new project using the Serverpod CLI. Run the following command in your terminal:

```bash
$ serverpod create notes
```

To start the server:

```bash
$ cd notes/notes_server
$ docker compose up --build --detach
$ dart bin/main.dart --apply-migrations
```

:::info

Make sure you have Docker running on your machine before executing these commands.

:::

## Serialize objects

Serverpod comes with a convenient way to create serializable objects with the help of code generation. These objects can easily be sent back and forth between the server and the client. This is managed by defining our objects in a YAML file which the code generator then parses and generates the necessary code for.

To define the structure of our Note object, we will create a YAML file called `note.yaml` inside the `lib/src/models` directory in your Serverpod project (`notes_server`). Add the following content in `note.yaml`:

```yaml
### Holds a note with a text written by the user.
class: Note
fields:
  ### The contents of the note.
  text: String
```

Let's take a closer look at the content of the `note.yaml` file:

- **class:** Specifies the name of the class to be generated, which in this case is **Note**.
- **fields:** This keyword indicates the beginning of the field definitions for the **Note** class.
- **text:** Defines a text field of type String in the Note class, in this minimal example we only have one field but you can add as many fields as you need.

Use the code generator to generate the code for the `Note` class from the definition in `note.yaml`. Run the following command from the root of your server project (`notes_server`):

```bash
$ serverpod generate
```

After the code generation process is complete, you can access the generated code for the `Note` class in `lib/src/generated/note.dart` inside your Serverpod project.

![Serverpod Insights](https://docs.serverpod.dev/img/tutorial/note-app/02-note-class-v2.png)

By implementing the `SerializableModel` class, the `Note` object becomes capable of automatic serialization and deserialization. This makes the `Note` object transmittable between the server and the client.

In simple terms, we have created a class, `Note`, that can hold information and can be passed within the server. Additionally, we can send this object to the client side of our application. Serverpod takes care of handling the conversion between the object and its serialized representation, making it convenient to work with and transfer data seamlessly.

## Create database tables

Serverpod provides built-in support for database integrations. By defining a database table named `note` in the YAML file using the `table` keyword, we can create database bindings for our `Note` class.

The updated content of the `note.yaml` file should look like this:

```yaml
### Holds a note with a text written by the user.
class: Note
table: note
fields:
  ### The contents of the note.
  text: String
```

Run the code generator again to generate the necessary code used to access the database table:

```bash
$ serverpod generate
```

Take a look at the updated `lib/src/generated/note.dart` file. You will notice that the code generator has added new methods for interacting with the database.

### Create database migration

To create the new `note` table in the database we will use the Serverpod migration system. Run the following command to generate a new database migration:

```bash
$ serverpod create-migration
```

This creates a new migration that contains a description of the database schema and SQL code to add the table. These files can be found in the `migrations` directory in your server project (`notes_server`).

### Apply database migration

To apply the database migration, start the server with the `--apply-migrations` command. We can also run the server in maintenance mode which will shut down the server as soon as its tasks are done.

```bash
$ dart run bin/main.dart --role maintenance --apply-migrations
```

Once command has executed successfully, the database table for storing the note data will have been created.

:::info

Any time you update the table definitions you have to create a migration and apply it to the database to update the database schema.

:::

## Create API endpoints

In Serverpod, endpoints are defined in the `endpoints` folder (`lib/src`) within your server project (`notes_server`). The code generator analyzes the code within these endpoints and generates a client library based on the defined functions. This client library is then used by your Flutter app (`notes_flutter`) to interact with your backend server.

Create a new file called `notes_endpoint.dart` inside `lib/src/endpoints` folder and add the following code:

```dart
import 'package:serverpod/server.dart';

import '../generated/protocol.dart';

class NotesEndpoint extends Endpoint {
  // Endpoint implementation goes here
}

```

In the above code, we import the necessary dependencies and import the generated `Note` class from the `protocol.dart` file. We also define the `NotesEndpoint` class, which extends the `Endpoint` class provided by Serverpod. This is required for the endpoint to be recognized by Serverpod's code generator.

### Define endpoints

To define an endpoint that can be called from the client, we need to create a method inside the `NotesEndpoint` class. This method must return a `Future` of a **serializable object**, **primitive datatype**, or **void**.

The first method parameter must be a `Session` object. This is a special object in Serverpod that contains information about the current session, as well as other helpful methods.

```dart
Future<void> example(Session session) async {
  // Endpoint implementation goes here
}
```

The method is also allowed to have any number of extra parameters. These parameters will be passed from the client when the endpoint is called. The parameters follow the same type restrictions as the return type.

### Store notes in the database

To store notes in the database we define a `createNote` method in the `NotesEndpoint` class. This method takes a `Note` object and stores it in the database. To make the method accessible from the app (`notes_flutter`), make sure that the first parameter passed to this method is a `Session` object.

```dart
Future<void> createNote(Session session, Note note) async {
  await Note.db.insertRow(session, note);
}
```

In the above code, we use the `Note.db.insertRow` method, created by `serverpod generate`, to insert the specified `Note` object into the database.

### Delete notes from the database

To delete notes from the database we define a `deleteNote` method in the `NotesEndpoint` class. The method takes a `Note` object which represents the note that needs to be deleted.

```dart
Future<void> deleteNote(Session session, Note note) async {
  await Note.db.deleteRow(session, note);
}
```

In the above code, we use the `Note.db.deleteRow` method to delete the specified `Note` object from the database.

### Fetch notes from the database

To retrieve all notes from the database we define the `getAllNotes` method in the `NotesEndpoint` class. This method retrieves all the notes from the database and returns them as a list of `Note` objects.

```dart
Future<List<Note>> getAllNotes(Session session) async {
  // By ordering by the id column, we always get the notes in the same order
  // and not in the order they were updated.
  return await Note.db.find(
    session,
    orderBy: (t) => t.id,
  );
}
```

In the code above, we use the `Note.db.find` method to retrieve all the notes from the database. By specifying `orderBy: (t) => t.id`, we ensure that the notes are always returned in the same order based on the id column.

Putting it all together you end up with a `notes_endpoint.dart` file that looks like this:

```dart
import 'package:serverpod/server.dart';

import '../generated/protocol.dart';

class NotesEndpoint extends Endpoint {
  Future<List<Note>> getAllNotes(Session session) async {
    // By ordering by the id column, we always get the notes in the same order
    // and not in the order they were updated.
    return await Note.db.find(
      session,
      orderBy: (t) => t.id,
    );
  }

  Future<void> createNote(Session session, Note note) async {
    await Note.db.insertRow(session, note);
  }

  Future<void> deleteNote(Session session, Note note) async {
    await Note.db.deleteRow(session, note);
  }
}
```

### Generate the client library

Congratulations! You have now created all the endpoints needed for the notes app, complete with database integration that persistently stores the notes.

Now run the code generator again to generate the client library for our endpoints. This needs to be run from the server directory `notes_server`.

```bash
$ serverpod generate
```

You can find the newly generated code in the client directory `notes_client`. You normally don't need to touch this package, but it's good to know where it is located.

## Build the Flutter app

It's time to build the `notes_flutter` app.

Open the `main.dart` file in your Flutter project (`notes_flutter`).

Locate the `MyHomePageState` class.

Update the `MyHomePageState` class by removing all the unnecessary code, so that it looks like this:

```dart
class MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
    );
  }
}

```

You can also remove the entire `_ResultDisplay` class from the file.

Your `main.dart` file should now look like this:

```dart
import 'package:notes_client/notes_client.dart';
import 'package:flutter/material.dart';
import 'package:serverpod_flutter/serverpod_flutter.dart';

// Sets up a singleton client object that can be used to talk to the server from
// anywhere in our app. The client is generated from your server code.
// The client is set up to connect to a Serverpod running on a local server on
// the default port. You will need to modify this to connect to staging or
// production servers.
var client = Client('http://localhost:8080/')
  ..connectivityMonitor = FlutterConnectivityMonitor();

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Serverpod Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Serverpod Example'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  MyHomePageState createState() => MyHomePageState();
}

class MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
    );
  }
}
```

### Fetch the notes from the server

To fetch all the notes from the server and handle potential connection failures, we first need to declare variables to hold the state inside the `MyHomePageState` class.

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

class MyHomePageState extends State<MyHomePage> {

  List<Note>? _notes;
  Exception? _connectionException;

  ...
}
```

Let's create a method to handle the connection failures. Call it `_connectionFailed`, it updates the state to set `_notes` to `null` and stores the thrown exception in `_connectionException`.

```dart
void _connectionFailed(dynamic exception) {
  setState(() {
    _notes = null;
    _connectionException = exception;
  });
}
```

Next, let's add a method for fetching notes from the server endpoint we created earlier. The method updates the state with the notes we received. If the call fails, we catch an exception and call the `_connectionFailed` method instead.

```dart
Future<void> _loadNotes() async {
  try {
    final notes = await client.notes.getAllNotes();
    setState(() {
      _notes = notes;
    });
  } catch (e) {
    _connectionFailed(e);
  }
}
```

Since, we want to call the `_loadNotes` method when the app is started, we override the `initState` method and call the `_loadNotes` method from there.

Add the `initState` method inside the `MyHomePageState` class.

```dart
@override
void initState() {
  super.initState();
  _loadNotes();
}
```

### Render the notes

To render the fetched notes in the app's main screen, we will update the `build` method inside the `MyHomePageState` class. Here is the modified code:

```dart
@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text(widget.title),
    ),
    body: _notes == null
      ? Container()
      : ListView.builder(
          itemCount: _notes!.length,
          itemBuilder: ((context, index) {
            return ListTile(
              title: Text(_notes![index].text),
            );
          }),
        ),
  );
}
```

The `_notes` variable is checked to determine if notes have been fetched from the server. If `_notes` is null, an empty `Container` widget is displayed as a placeholder.

If `_notes` is not null, a `ListView.builder` widget is used to render the notes. The `itemCount` property is set to the length of the `_notes` list, and the itemBuilder callback is responsible for building the individual `ListTile` widgets.

Inside the `itemBuilder` callback, each note's text is displayed in a `ListTile` using the `Text` widget.

### Create new notes

To create new notes we need to access the `createNote` method of our `notes` endpoint. To do this, we create a helper method called `_createNote` inside `MyHomePageState` class that takes a `Note` object as a parameter.

```dart
Future<void> _createNote(Note note) async {
  try {
    await client.notes.createNote(note);
    await _loadNotes();
  } catch (e) {
    _connectionFailed(e);
  }
}
```

The `_createNote` method calls the `createNote` method (`lib/src/endpoints/notes_endpoint.dart` in `notes_server`) to store the note in the database on the server. Then `_loadNotes` is called to refresh our list of notes.

The user needs graphical interface to create notes, so let's create a dialog for this. Create a new file called `note_dialog.dart` in the `lib` directory of your Flutter app. Add the following code:

Code: note\_dialog.dart

```dart
import 'package:flutter/material.dart';

void showNoteDialog({
  required BuildContext context,
  String text = '',
  required ValueChanged<String> onSaved,
}) {
  showDialog(
    context: context,
    builder: (context) => NoteDialog(
      text: text,
      onSaved: onSaved,
    ),
  );
}

class NoteDialog extends StatefulWidget {
  const NoteDialog({
    required this.text,
    required this.onSaved,
    super.key,
  });

  final String text;
  final ValueChanged<String> onSaved;

  @override
  NoteDialogState createState() => NoteDialogState();
}

class NoteDialogState extends State<NoteDialog> {
  final TextEditingController controller = TextEditingController();

  @override
  void initState() {
    super.initState();
    controller.text = widget.text;
  }

  @override
  Widget build(BuildContext context) {
    return Dialog(
      child: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            Expanded(
              child: TextField(
                controller: controller,
                expands: true,
                maxLines: null,
                minLines: null,
                decoration: const InputDecoration(
                  border: InputBorder.none,
                  hintText: 'Write your note here...',
                ),
              ),
            ),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: () {
                widget.onSaved(controller.text);
                Navigator.of(context).pop();
              },
              child: const Text('Save'),
            ),
          ],
        ),
      ),
    );
  }
}
```

The dialog is needed but we will skip going into details of how it works as this is just normal Flutter code. The gist is that we have a function that triggers an input dialog and a callback for when the user saves the input.

We need a button to trigger this dialog and a floating action button would be great for this. Add the following code to the build method inside the `MyHomePageState` class:

```dart
@override
Widget build(BuildContext context) {
  return Scaffold(
    ...
    floatingActionButton: _notes == null
      ? null
      : FloatingActionButton(
          onPressed: () {
            showNoteDialog(
              context: context,
              onSaved: (text) {
                var note = Note(
                  text: text,
                );
                _notes!.add(note);

                _createNote(note);
              },
            );
          },
          child: const Icon(Icons.add),
        ),
  );
}
```

Also import the dialog:

```dart
import 'package:notes_flutter/note_dialog.dart';
```

In the above code, we trigger the note dialog when the action button is pressed and then save the note in the `onSaved` callback. To make the UI feel more responsive, we add the changes to the notes list before calling `_createNote`. This way the note will be added to the list immediately and then updated when the server responds.

Finally, add the loading screen to the project. Create a new file called `loading_screen.dart` in the `lib` directory of your Flutter app and add the following code.

Code: loading\_screen.dart

```dart
import 'package:flutter/material.dart';

class LoadingScreen extends StatelessWidget {
  const LoadingScreen({
    this.exception,
    required this.onTryAgain,
    super.key,
  });

  final Exception? exception;
  final VoidCallback onTryAgain;

  @override
  Widget build(BuildContext context) {
    if (exception != null) {
      return Center(
        child: ElevatedButton(
          onPressed: onTryAgain,
          child: const Text('Try again'),
        ),
      );
    } else {
      return const Center(
        child: CircularProgressIndicator(),
      );
    }
  }
}

```

Replace the empty `Container` with the loading screen in the `build` method.

Code: main.dart

```dart
import 'package:notes_client/notes_client.dart';
import 'package:flutter/material.dart';
import 'package:serverpod_flutter/serverpod_flutter.dart';

import 'note_dialog.dart';

// Sets up a singleton client object that can be used to talk to the server from
// anywhere in our app. The client is generated from your server code.
// The client is set up to connect to a Serverpod running on a local server on
// the default port. You will need to modify this to connect to staging or
// production servers.
var client = Client('http://localhost:8080/')
  ..connectivityMonitor = FlutterConnectivityMonitor();

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Notes',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Notes'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  MyHomePageState createState() => MyHomePageState();
}

class MyHomePageState extends State<MyHomePage> {
  // This field holds the list of notes that we've received from the server or
  // null if no notes have been received yet.
  List<Note>? _notes;

  // If the connection to the server fails, this field will hold the exception
  // that was thrown.
  Exception? _connectionException;

  @override
  void initState() {
    super.initState();
    _loadNotes();
  }

  Future<void> _loadNotes() async {
    try {
      final notes = await client.notes.getAllNotes();
      setState(() {
        _notes = notes;
      });
    } catch (e) {
      _connectionFailed(e);
    }
  }

  Future<void> _createNote(Note note) async {
    try {
      await client.notes.createNote(note);
      await _loadNotes();
    } catch (e) {
      _connectionFailed(e);
    }
  }

  void _connectionFailed(dynamic exception) {
    // If the connection to the server fails, we clear the list of notes and
    // store the exception that was thrown. This will make the loading screen
    // appear and show a button to try again.

    // In a real app you would probably want to do more complete error handling.
    setState(() {
      _notes = null;
      _connectionException = exception;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: _notes == null
          ? LoadingScreen(
              exception: _connectionException,
              onTryAgain: _loadNotes,
            )
          : ListView.builder(
              itemCount: _notes!.length,
              itemBuilder: ((context, index) {
                return ListTile(
                  title: Text(_notes![index].text),
                );
              }),
            ),
      floatingActionButton: _notes == null
          ? null
          : FloatingActionButton(
              onPressed: () {
                // When we tap the floating action button we want to show a
                // dialog where we can create a new note.
                showNoteDialog(
                  context: context,
                  onSaved: (text) {
                    var note = Note(
                      text: text,
                    );

                    // Add the note to the list of notes before we've received
                    // a response from the server which makes the UI feel more
                    // responsive.
                    _notes!.add(note);

                    // Actually create the note on the server.
                    _createNote(note);
                  },
                );
              },
              child: const Icon(Icons.add),
            ),
    );
  }
}
```

Also import the loading screen:

```dart
import 'package:notes_flutter/note_dialog.dart';
```

### Run the app

Start the database and server:
Make sure you reboot the server if you started it earlier.

```bash
$ cd notes_server
$ docker compose up --build --detach
$ dart bin/main.dart --apply-migrations
```

Start the Flutter app in Chrome (or the platform of your choice):

```bash
$ cd notes_flutter
$ flutter run -d chrome
```

[Watch the video](https://docs.serverpod.dev/img/tutorial/note-app/03-note-create.mp4)

### Delete notes

Implementing the delete functionality is very similar to the create functionality. We will add a delete button to each note and then call the delete endpoint when the button is pressed. First, let's add a helper method, `_deleteNote` inside the `MyHomePageState` class to call the endpoint:

```dart
Future<void> _deleteNote(Note note) async {
  try {
    await client.notes.deleteNote(note);
    await _loadNotes();
  } catch (e) {
    _connectionFailed(e);
  }
}
```

The `_deleteNote` method calls the `deleteNote` endpoint to delete the note from the database on the server. Then `_loadNotes` is called to refresh the notes list.

Next in our `ListTile` we add a delete button and call the `_deleteNote` method when the button is pressed. Just as before we update the local state first to make the UI feel more responsive.

```dart
ListTile(
  ...
  trailing: IconButton(
    icon: const Icon(Icons.delete),
    onPressed: () {
      var note = _notes![index];

      setState(() {
        _notes!.remove(note);
      });

      _deleteNote(note);
    },
  ),
),
```

We can now delete all the notes we have created.

[Watch the video](https://docs.serverpod.dev/img/tutorial/note-app/04-note-delete.mp4)

### Edit notes

We leave this part as an exercise for the reader. Try to see if you can implement the edit functionality. You can use the `showNoteDialog` method we created earlier to show the dialog for editing the note. If you followed along so far you have all the tools you need to implement this feature!

Give it a go, in case you need some help you can look at the ([full code example](https://github.com/serverpod/notes)).

## Summary

In this tutorial, you learned how to build a simple note-taking app using the Serverpod backend framework. You started by setting up the necessary tools and environment to work with Serverpod. Then, you covered various aspects of building an app, including creating serializable objects, creating database tables, and creating API endpoints for performing CRUD operations.

You also learned how to use Serverpod's code generator and how to use the generated client library to connect the Flutter app to the server. By establishing this connection, you were able to fetch data from the server and display it in the app.

Throughout the tutorial, you gained an understanding of how Serverpod simplifies the development process by automatically handling the serialization and deserialization of objects, managing database tables, and seamlessly integrating with Flutter. You have acquired the foundational knowledge to build powerful and scalable server-side applications using Serverpod.

Congratulations on completing this tutorial. You are now equipped with the skills to build your own server-side applications using Serverpod. Happy coding!

Want to learn more? Check out some of our other tutorials, or the tutorials created by our community.
