# Create your first app

> Start an HTTP server, return a JSON response, reject invalid input and test it without opening a port.

- Package: `@db3.ai/app/server`
- Canonical page: [https://db3.ai/docs/create-app](https://db3.ai/docs/create-app)
- Markdown: [https://db3.ai/docs/create-app.md](https://db3.ai/docs/create-app.md)
- Framework source of truth: `packages/app/src/server/README.md`

<a id="setup"></a>

## Set up the files

Complete Installation first, including Fastify and the development tools. Then copy the shipped example files into your app. This is ordinary application code you can edit, not a second framework repository.

- [Installation and prerequisites](https://db3.ai/docs/installation.md)

### Copy the shipped example

```bash
mkdir -p examples
cp -R node_modules/@db3.ai/app/src/server/examples/. examples/
```

<a id="layout"></a>

## Your application layout

This is your standalone app, not the framework source tree. After copying the two server files and adding the test below, it has this layout. You do not need a `packages/app` directory or Scout checkout.

Keep the code in `examples/` while learning. When you move it into your own source folder, update the start command and test import together. Add a private `.env` only when a database-backed guide needs it, and exclude it and `node_modules/` from Git.

### Standalone app after adding the test

```txt
my-db3-app/
	package.json
	package-lock.json
	examples/
		createFirstServer.ts
		startFirstServer.ts
	tests/
		server/
			firstApplication.test.ts
```

<a id="application"></a>

## Create the application

`createFirstServer()` returns a server without listening yet. It creates one framework `App`, attaches cleanup to Fastify, and registers health and greeting routes. Keeping construction separate from listening is what makes it easy to test.

The greeting route validates its parameters and wraps its work in `requestContext.run()`. Request-owned values stay in that asynchronous context rather than becoming process-global state.

### examples/createFirstServer.ts

```typescript
import Fastify, { type FastifyInstance } from 'fastify';
import { App } from '@db3.ai/app/server';

/**
 * Creates a small HTTP application without opening a port or database.
 *
 * The caller owns listen/close, so the same app can serve requests in a process
 * or be exercised through Fastify.inject in an isolated test.
 *
 * @param greeting - Application-owned greeting, configurable at process boot.
 * @returns HTTP server with framework cleanup attached to its close lifecycle.
 */
export function createFirstServer(greeting = 'Hello'): FastifyInstance {
	const application = new App({ config: { greeting } });
	const server = Fastify();
	server.addHook('onClose', async () => { await application.close(); });
	server.get('/health', async () => ({ status: 'ready' }));
	server.get<{ Params: { name: string } }>('/hello/:name', {
		schema: { params: { type: 'object', required: ['name'], properties: { name: { type: 'string', minLength: 1, maxLength: 80 } } } },
	}, async request => application.requestContext.run(async () => {
		application.requestContext.set('requestId', request.id);
		return { message: `${application.config.get<string>('greeting')}, ${request.params.name}!` };
	}));
	return server;
}
```

<a id="process"></a>

## Own the process boundary

The entry point chooses the greeting and port, starts a local listener and closes it on Ctrl-C or SIGTERM. It listens on `127.0.0.1`; production binding, HTTPS, supervision and proxy trust need deliberate deployment configuration.

### examples/startFirstServer.ts

```typescript
import { createFirstServer } from './createFirstServer';

const port = Number(process.env.PORT ?? 3000);
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error('PORT must be an integer from 1 to 65535.');
const server = createFirstServer(process.env.GREETING ?? 'Hello');

/** Closes the listener and framework resources when the local process stops. */
async function shutdown(): Promise<void> {
	await server.close();
}

process.once('SIGINT', shutdown);
process.once('SIGTERM', shutdown);
try {
	console.log(await server.listen({ port, host: '127.0.0.1' }));
} catch (error) {
	await server.close();
	throw error;
}
```

<a id="run"></a>

## Run the app

Run this from your app directory. Leave the process running while you try the requests in another terminal. No database tables are created.

### Start locally

```bash
npx tsx examples/startFirstServer.ts
```

<a id="use"></a>

## Make a request

The health route returns `{"status":"ready"}`. The greeting returns `{"message":"Hello, Ada!"}`. An unknown route returns 404; a name longer than 80 characters returns 400.

### In a second terminal

```bash
curl http://127.0.0.1:3000/health
curl http://127.0.0.1:3000/hello/Ada
```

<a id="testing"></a>

## Testing

Create a `tests/server` directory and save the test below as `firstApplication.test.ts`. It imports the example you copied into `examples/`. Keep the same folder layout so the relative import resolves.

Run from the application root with the development dependencies from Installation. These are consumer tests, not commands that assume a framework checkout.

### tests/server/firstApplication.test.ts

```typescript
import { expect, it } from 'vitest';
import { createFirstServer } from '../../examples/createFirstServer';

it('serves a useful route and rejects an invalid name without a database or socket', async () => {
	const server = createFirstServer('Welcome');
	try {
		const health = await server.inject('/health');
		expect(health.statusCode).toBe(200);
		expect(health.json()).toEqual({ status: 'ready' });
		const greeting = await server.inject('/hello/Ada');
		expect(greeting.statusCode).toBe(200);
		expect(greeting.json()).toEqual({ message: 'Welcome, Ada!' });
		expect((await server.inject(`/hello/${'x'.repeat(81)}`)).statusCode).toBe(400);
		expect((await server.inject('/missing')).statusCode).toBe(404);
	} finally {
		await server.close();
	}
});
```

<a id="run-tests"></a>

## Run your tests

The test uses `server.inject()` against the same application factory. It covers a successful response, configuration, invalid input and a missing route, then closes the framework. No mock App, TCP port or SQL account is involved.

### Run your copied test and check types

```bash
npx vitest run tests/server/firstApplication.test.ts
npx tsc --noEmit --target ES2022 --module ESNext --moduleResolution Bundler --types node --skipLibCheck examples/*.ts
```

<a id="next"></a>

## Build the next feature

Change the greeting and run the test. Add another route and its success and failure cases. Move your own application code out of `examples/` when you settle on a structure; keep the test import in sync.

This is a working backend starting point, not a full SaaS shell. Authentication, durable records, file ownership and background processing belong to the next guides.

- [App lifecycle and service configuration](https://db3.ai/docs/app.md)
- [Add Auth](https://db3.ai/docs/auth.md)
- [Write files with Storage](https://db3.ai/docs/storage.md)

## Behavioural verification
Runs health, greeting, validation and not-found requests through the real HTTP application.
- Behaviour test: `packages/app/src/server/tests/examples/firstApplication.test.ts`
- Repository test command (framework checkout only): `npm run test:service --workspace @platform/app -- server --maxWorkers=1`
- In an installed application, use the walkthrough commands instead of this repository test.
- Expected outcome: The guide test passes against the real framework components.
- Environment: Node.js 24 or newer; Fastify. No database, external service or listening port.

## Related documentation
- [Install the framework](https://db3.ai/docs/installation.md): Install one runtime package, then build a small HTTP app. Add a database and other services when your feature needs them.
- [App](https://db3.ai/docs/app.md): Create one application at boot. Configure its services, use request-local state and close the resources you own.
- [Auth](https://db3.ai/docs/auth.md): Give an account one or more login methods. Issue bearer sessions, reset passwords and revoke access without mixing identity with credentials.
- [Storage](https://db3.ai/docs/storage.md): Write files to a named disk, stream large payloads and keep paths relative to storage. Add Media when files need durable identities and ownership metadata.

## Guidance for AI tools
Use the documented public import `@db3.ai/app/server` and its exported types. Prefer the source-backed examples and behavioural outcomes above over invented APIs or source-relative internal imports.
