├── .editorconfig ├── .env.example ├── .gitignore ├── README.md ├── ace.js ├── adonisrc.ts ├── app ├── exceptions │ └── handler.ts └── middleware │ └── container_bindings_middleware.ts ├── bin ├── console.ts ├── server.ts └── test.ts ├── config ├── app.ts ├── bodyparser.ts ├── hash.ts ├── logger.ts ├── session.ts ├── shield.ts ├── static.ts └── vite.ts ├── eslint.config.js ├── package-lock.json ├── package.json ├── resources ├── css │ └── app.css ├── js │ └── app.js └── views │ └── pages │ ├── errors │ ├── not_found.edge │ └── server_error.edge │ └── home.edge ├── start ├── env.ts ├── kernel.ts └── routes.ts ├── tests └── bootstrap.ts ├── tsconfig.json └── vite.config.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.json] 12 | insert_final_newline = unset 13 | 14 | [**.min.js] 15 | indent_style = unset 16 | insert_final_newline = unset 17 | 18 | [MakeFile] 19 | indent_style = space 20 | 21 | [*.md] 22 | trim_trailing_whitespace = false 23 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | TZ=UTC 2 | PORT=3333 3 | HOST=localhost 4 | LOG_LEVEL=info 5 | APP_KEY=zKXHe-Ahdb7aPK1ylAJlRgTefktEaACi 6 | NODE_ENV=development 7 | SESSION_DRIVER=cookie 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies and AdonisJS build 2 | node_modules 3 | build 4 | tmp 5 | 6 | # Secrets 7 | .env 8 | .env.local 9 | .env.production.local 10 | .env.development.local 11 | 12 | # Frontend assets compiled code 13 | public/assets 14 | 15 | # Build tools specific 16 | npm-debug.log 17 | yarn-error.log 18 | 19 | # Editors specific 20 | .fleet 21 | .idea 22 | .vscode 23 | 24 | # Platform specific 25 | .DS_Store 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AdonisJS Web Starter Kit 2 | 3 | This repo contains an AdonisJS application tailored for building a server-side rendered application using the [Edge template engine](https://edgejs.dev). 4 | 5 | ## What's included 6 | 7 | - TypeScript setup with commands to run developments server using `ts-node + swc` and create production build. 8 | - ESLint and Prettier setup extending the [AdonisJS tooling config](https://github.com/adonisjs/tooling-config) presets. 9 | - Ace command line framework. 10 | - Everything else you get with the core of AdonisJS. 11 | 12 | On top of the framework core and dev-tooling, the following features are enabled by the web starter kit. 13 | 14 | - Lucid ORM ( Installed, but not configured ) 15 | - Auth module ( Installed, but not configured ) 16 | - CSRF protection 17 | - Edge template engine 18 | - VineJS for validations 19 | - Static files server 20 | - Vite for bundling and serving frontend assets 21 | 22 | ## Usage 23 | 24 | You can create a new app using the `web` boilerplate by executing the following command. The command will perform the following steps. 25 | 26 | - Clone the repo 27 | - Install dependencies 28 | - Copy `.env.example` to `.env` 29 | - Set app key using `node ace generate:key` command. 30 | - Configure `@adonisjs/lucid` package. 31 | - Configure `@adonisjs/auth` package. 32 | 33 | ```sh 34 | npm init adonisjs@latest -- -K=web 35 | ``` 36 | 37 | ### Configuring Lucid database dialect 38 | 39 | By default, the `npm init adonisjs@latest` command configures Lucid to use `sqlite`. However, you can define a custom database dialect as follows. 40 | 41 | ```sh 42 | npm init adonisjs@latest -- -K=web --db=postgres 43 | ``` 44 | 45 | Available options for the `--db` flag. 46 | 47 | - sqlite 48 | - postgres 49 | - mysql 50 | - mssql 51 | 52 | ### Configuring Auth package guard 53 | 54 | By default, the `npm init adonisjs@latest` command configures the Auth package to use `session` guard. However, you can define a custom auth guard as follows. 55 | 56 | ```sh 57 | npm init adonisjs@latest -- -K=web --auth-guard=access_tokens 58 | ``` 59 | 60 | Available options for the `--auth-guard` flag. 61 | 62 | - session 63 | - basic_auth 64 | - access_tokens 65 | -------------------------------------------------------------------------------- /ace.js: -------------------------------------------------------------------------------- 1 | /* 2 | |-------------------------------------------------------------------------- 3 | | JavaScript entrypoint for running ace commands 4 | |-------------------------------------------------------------------------- 5 | | 6 | | DO NOT MODIFY THIS FILE AS IT WILL BE OVERRIDDEN DURING THE BUILD 7 | | PROCESS. 8 | | 9 | | See docs.adonisjs.com/guides/typescript-build-process#creating-production-build 10 | | 11 | | Since, we cannot run TypeScript source code using "node" binary, we need 12 | | a JavaScript entrypoint to run ace commands. 13 | | 14 | | This file registers the "ts-node/esm" hook with the Node.js module system 15 | | and then imports the "bin/console.ts" file. 16 | | 17 | */ 18 | 19 | /** 20 | * Register hook to process TypeScript files using ts-node 21 | */ 22 | import 'ts-node-maintained/register/esm' 23 | 24 | /** 25 | * Import ace console entrypoint 26 | */ 27 | await import('./bin/console.js') 28 | -------------------------------------------------------------------------------- /adonisrc.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@adonisjs/core/app' 2 | 3 | export default defineConfig({ 4 | /* 5 | |-------------------------------------------------------------------------- 6 | | Experimental flags 7 | |-------------------------------------------------------------------------- 8 | | 9 | | The following features will be enabled by default in the next major release 10 | | of AdonisJS. You can opt into them today to avoid any breaking changes 11 | | during upgrade. 12 | | 13 | */ 14 | experimental: { 15 | mergeMultipartFieldsAndFiles: true, 16 | shutdownInReverseOrder: true, 17 | }, 18 | 19 | /* 20 | |-------------------------------------------------------------------------- 21 | | Commands 22 | |-------------------------------------------------------------------------- 23 | | 24 | | List of ace commands to register from packages. The application commands 25 | | will be scanned automatically from the "./commands" directory. 26 | | 27 | */ 28 | commands: [() => import('@adonisjs/core/commands')], 29 | 30 | /* 31 | |-------------------------------------------------------------------------- 32 | | Service providers 33 | |-------------------------------------------------------------------------- 34 | | 35 | | List of service providers to import and register when booting the 36 | | application 37 | | 38 | */ 39 | providers: [ 40 | () => import('@adonisjs/core/providers/app_provider'), 41 | () => import('@adonisjs/core/providers/hash_provider'), 42 | { 43 | file: () => import('@adonisjs/core/providers/repl_provider'), 44 | environment: ['repl', 'test'], 45 | }, 46 | () => import('@adonisjs/core/providers/vinejs_provider'), 47 | () => import('@adonisjs/core/providers/edge_provider'), 48 | () => import('@adonisjs/session/session_provider'), 49 | () => import('@adonisjs/vite/vite_provider'), 50 | () => import('@adonisjs/shield/shield_provider'), 51 | () => import('@adonisjs/static/static_provider'), 52 | ], 53 | 54 | /* 55 | |-------------------------------------------------------------------------- 56 | | Preloads 57 | |-------------------------------------------------------------------------- 58 | | 59 | | List of modules to import before starting the application. 60 | | 61 | */ 62 | preloads: [() => import('#start/routes'), () => import('#start/kernel')], 63 | 64 | /* 65 | |-------------------------------------------------------------------------- 66 | | Tests 67 | |-------------------------------------------------------------------------- 68 | | 69 | | List of test suites to organize tests by their type. Feel free to remove 70 | | and add additional suites. 71 | | 72 | */ 73 | tests: { 74 | suites: [ 75 | { 76 | files: ['tests/unit/**/*.spec(.ts|.js)'], 77 | name: 'unit', 78 | timeout: 2000, 79 | }, 80 | { 81 | files: ['tests/functional/**/*.spec(.ts|.js)'], 82 | name: 'functional', 83 | timeout: 30000, 84 | }, 85 | ], 86 | forceExit: false, 87 | }, 88 | 89 | metaFiles: [ 90 | { 91 | pattern: 'resources/views/**/*.edge', 92 | reloadServer: false, 93 | }, 94 | { 95 | pattern: 'public/**', 96 | reloadServer: false, 97 | }, 98 | ], 99 | 100 | assetsBundler: false, 101 | hooks: { 102 | onBuildStarting: [() => import('@adonisjs/vite/build_hook')], 103 | }, 104 | }) 105 | -------------------------------------------------------------------------------- /app/exceptions/handler.ts: -------------------------------------------------------------------------------- 1 | import app from '@adonisjs/core/services/app' 2 | import { HttpContext, ExceptionHandler } from '@adonisjs/core/http' 3 | import type { StatusPageRange, StatusPageRenderer } from '@adonisjs/core/types/http' 4 | 5 | export default class HttpExceptionHandler extends ExceptionHandler { 6 | /** 7 | * In debug mode, the exception handler will display verbose errors 8 | * with pretty printed stack traces. 9 | */ 10 | protected debug = !app.inProduction 11 | 12 | /** 13 | * Status pages are used to display a custom HTML pages for certain error 14 | * codes. You might want to enable them in production only, but feel 15 | * free to enable them in development as well. 16 | */ 17 | protected renderStatusPages = app.inProduction 18 | 19 | /** 20 | * Status pages is a collection of error code range and a callback 21 | * to return the HTML contents to send as a response. 22 | */ 23 | protected statusPages: Record = { 24 | '404': (error, { view }) => { 25 | return view.render('pages/errors/not_found', { error }) 26 | }, 27 | '500..599': (error, { view }) => { 28 | return view.render('pages/errors/server_error', { error }) 29 | }, 30 | } 31 | 32 | /** 33 | * The method is used for handling errors and returning 34 | * response to the client 35 | */ 36 | async handle(error: unknown, ctx: HttpContext) { 37 | return super.handle(error, ctx) 38 | } 39 | 40 | /** 41 | * The method is used to report error to the logging service or 42 | * the a third party error monitoring service. 43 | * 44 | * @note You should not attempt to send a response from this method. 45 | */ 46 | async report(error: unknown, ctx: HttpContext) { 47 | return super.report(error, ctx) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/middleware/container_bindings_middleware.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '@adonisjs/core/logger' 2 | import { HttpContext } from '@adonisjs/core/http' 3 | import { NextFn } from '@adonisjs/core/types/http' 4 | 5 | /** 6 | * The container bindings middleware binds classes to their request 7 | * specific value using the container resolver. 8 | * 9 | * - We bind "HttpContext" class to the "ctx" object 10 | * - And bind "Logger" class to the "ctx.logger" object 11 | */ 12 | export default class ContainerBindingsMiddleware { 13 | handle(ctx: HttpContext, next: NextFn) { 14 | ctx.containerResolver.bindValue(HttpContext, ctx) 15 | ctx.containerResolver.bindValue(Logger, ctx.logger) 16 | 17 | return next() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /bin/console.ts: -------------------------------------------------------------------------------- 1 | /* 2 | |-------------------------------------------------------------------------- 3 | | Ace entry point 4 | |-------------------------------------------------------------------------- 5 | | 6 | | The "console.ts" file is the entrypoint for booting the AdonisJS 7 | | command-line framework and executing commands. 8 | | 9 | | Commands do not boot the application, unless the currently running command 10 | | has "options.startApp" flag set to true. 11 | | 12 | */ 13 | 14 | import 'reflect-metadata' 15 | import { Ignitor, prettyPrintError } from '@adonisjs/core' 16 | 17 | /** 18 | * URL to the application root. AdonisJS need it to resolve 19 | * paths to file and directories for scaffolding commands 20 | */ 21 | const APP_ROOT = new URL('../', import.meta.url) 22 | 23 | /** 24 | * The importer is used to import files in context of the 25 | * application. 26 | */ 27 | const IMPORTER = (filePath: string) => { 28 | if (filePath.startsWith('./') || filePath.startsWith('../')) { 29 | return import(new URL(filePath, APP_ROOT).href) 30 | } 31 | return import(filePath) 32 | } 33 | 34 | new Ignitor(APP_ROOT, { importer: IMPORTER }) 35 | .tap((app) => { 36 | app.booting(async () => { 37 | await import('#start/env') 38 | }) 39 | app.listen('SIGTERM', () => app.terminate()) 40 | app.listenIf(app.managedByPm2, 'SIGINT', () => app.terminate()) 41 | }) 42 | .ace() 43 | .handle(process.argv.splice(2)) 44 | .catch((error) => { 45 | process.exitCode = 1 46 | prettyPrintError(error) 47 | }) 48 | -------------------------------------------------------------------------------- /bin/server.ts: -------------------------------------------------------------------------------- 1 | /* 2 | |-------------------------------------------------------------------------- 3 | | HTTP server entrypoint 4 | |-------------------------------------------------------------------------- 5 | | 6 | | The "server.ts" file is the entrypoint for starting the AdonisJS HTTP 7 | | server. Either you can run this file directly or use the "serve" 8 | | command to run this file and monitor file changes 9 | | 10 | */ 11 | 12 | import 'reflect-metadata' 13 | import { Ignitor, prettyPrintError } from '@adonisjs/core' 14 | 15 | /** 16 | * URL to the application root. AdonisJS need it to resolve 17 | * paths to file and directories for scaffolding commands 18 | */ 19 | const APP_ROOT = new URL('../', import.meta.url) 20 | 21 | /** 22 | * The importer is used to import files in context of the 23 | * application. 24 | */ 25 | const IMPORTER = (filePath: string) => { 26 | if (filePath.startsWith('./') || filePath.startsWith('../')) { 27 | return import(new URL(filePath, APP_ROOT).href) 28 | } 29 | return import(filePath) 30 | } 31 | 32 | new Ignitor(APP_ROOT, { importer: IMPORTER }) 33 | .tap((app) => { 34 | app.booting(async () => { 35 | await import('#start/env') 36 | }) 37 | app.listen('SIGTERM', () => app.terminate()) 38 | app.listenIf(app.managedByPm2, 'SIGINT', () => app.terminate()) 39 | }) 40 | .httpServer() 41 | .start() 42 | .catch((error) => { 43 | process.exitCode = 1 44 | prettyPrintError(error) 45 | }) 46 | -------------------------------------------------------------------------------- /bin/test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | |-------------------------------------------------------------------------- 3 | | Test runner entrypoint 4 | |-------------------------------------------------------------------------- 5 | | 6 | | The "test.ts" file is the entrypoint for running tests using Japa. 7 | | 8 | | Either you can run this file directly or use the "test" 9 | | command to run this file and monitor file changes. 10 | | 11 | */ 12 | 13 | process.env.NODE_ENV = 'test' 14 | 15 | import 'reflect-metadata' 16 | import { Ignitor, prettyPrintError } from '@adonisjs/core' 17 | import { configure, processCLIArgs, run } from '@japa/runner' 18 | 19 | /** 20 | * URL to the application root. AdonisJS need it to resolve 21 | * paths to file and directories for scaffolding commands 22 | */ 23 | const APP_ROOT = new URL('../', import.meta.url) 24 | 25 | /** 26 | * The importer is used to import files in context of the 27 | * application. 28 | */ 29 | const IMPORTER = (filePath: string) => { 30 | if (filePath.startsWith('./') || filePath.startsWith('../')) { 31 | return import(new URL(filePath, APP_ROOT).href) 32 | } 33 | return import(filePath) 34 | } 35 | 36 | new Ignitor(APP_ROOT, { importer: IMPORTER }) 37 | .tap((app) => { 38 | app.booting(async () => { 39 | await import('#start/env') 40 | }) 41 | app.listen('SIGTERM', () => app.terminate()) 42 | app.listenIf(app.managedByPm2, 'SIGINT', () => app.terminate()) 43 | }) 44 | .testRunner() 45 | .configure(async (app) => { 46 | const { runnerHooks, ...config } = await import('../tests/bootstrap.js') 47 | 48 | processCLIArgs(process.argv.splice(2)) 49 | configure({ 50 | ...app.rcFile.tests, 51 | ...config, 52 | ...{ 53 | setup: runnerHooks.setup, 54 | teardown: runnerHooks.teardown.concat([() => app.terminate()]), 55 | }, 56 | }) 57 | }) 58 | .run(() => run()) 59 | .catch((error) => { 60 | process.exitCode = 1 61 | prettyPrintError(error) 62 | }) 63 | -------------------------------------------------------------------------------- /config/app.ts: -------------------------------------------------------------------------------- 1 | import env from '#start/env' 2 | import app from '@adonisjs/core/services/app' 3 | import { Secret } from '@adonisjs/core/helpers' 4 | import { defineConfig } from '@adonisjs/core/http' 5 | 6 | /** 7 | * The app key is used for encrypting cookies, generating signed URLs, 8 | * and by the "encryption" module. 9 | * 10 | * The encryption module will fail to decrypt data if the key is lost or 11 | * changed. Therefore it is recommended to keep the app key secure. 12 | */ 13 | export const appKey = new Secret(env.get('APP_KEY')) 14 | 15 | /** 16 | * The configuration settings used by the HTTP server 17 | */ 18 | export const http = defineConfig({ 19 | generateRequestId: true, 20 | allowMethodSpoofing: false, 21 | 22 | /** 23 | * Enabling async local storage will let you access HTTP context 24 | * from anywhere inside your application. 25 | */ 26 | useAsyncLocalStorage: false, 27 | 28 | /** 29 | * Manage cookies configuration. The settings for the session id cookie are 30 | * defined inside the "config/session.ts" file. 31 | */ 32 | cookie: { 33 | domain: '', 34 | path: '/', 35 | maxAge: '2h', 36 | httpOnly: true, 37 | secure: app.inProduction, 38 | sameSite: 'lax', 39 | }, 40 | }) 41 | -------------------------------------------------------------------------------- /config/bodyparser.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@adonisjs/core/bodyparser' 2 | 3 | const bodyParserConfig = defineConfig({ 4 | /** 5 | * The bodyparser middleware will parse the request body 6 | * for the following HTTP methods. 7 | */ 8 | allowedMethods: ['POST', 'PUT', 'PATCH', 'DELETE'], 9 | 10 | /** 11 | * Config for the "application/x-www-form-urlencoded" 12 | * content-type parser 13 | */ 14 | form: { 15 | convertEmptyStringsToNull: true, 16 | types: ['application/x-www-form-urlencoded'], 17 | }, 18 | 19 | /** 20 | * Config for the JSON parser 21 | */ 22 | json: { 23 | convertEmptyStringsToNull: true, 24 | types: [ 25 | 'application/json', 26 | 'application/json-patch+json', 27 | 'application/vnd.api+json', 28 | 'application/csp-report', 29 | ], 30 | }, 31 | 32 | /** 33 | * Config for the "multipart/form-data" content-type parser. 34 | * File uploads are handled by the multipart parser. 35 | */ 36 | multipart: { 37 | /** 38 | * Enabling auto process allows bodyparser middleware to 39 | * move all uploaded files inside the tmp folder of your 40 | * operating system 41 | */ 42 | autoProcess: true, 43 | convertEmptyStringsToNull: true, 44 | processManually: [], 45 | 46 | /** 47 | * Maximum limit of data to parse including all files 48 | * and fields 49 | */ 50 | limit: '20mb', 51 | types: ['multipart/form-data'], 52 | }, 53 | }) 54 | 55 | export default bodyParserConfig 56 | -------------------------------------------------------------------------------- /config/hash.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, drivers } from '@adonisjs/core/hash' 2 | 3 | const hashConfig = defineConfig({ 4 | default: 'scrypt', 5 | 6 | list: { 7 | scrypt: drivers.scrypt({ 8 | cost: 16384, 9 | blockSize: 8, 10 | parallelization: 1, 11 | maxMemory: 33554432, 12 | }), 13 | }, 14 | }) 15 | 16 | export default hashConfig 17 | 18 | /** 19 | * Inferring types for the list of hashers you have configured 20 | * in your application. 21 | */ 22 | declare module '@adonisjs/core/types' { 23 | export interface HashersList extends InferHashers {} 24 | } 25 | -------------------------------------------------------------------------------- /config/logger.ts: -------------------------------------------------------------------------------- 1 | import env from '#start/env' 2 | import app from '@adonisjs/core/services/app' 3 | import { defineConfig, targets } from '@adonisjs/core/logger' 4 | 5 | const loggerConfig = defineConfig({ 6 | default: 'app', 7 | 8 | /** 9 | * The loggers object can be used to define multiple loggers. 10 | * By default, we configure only one logger (named "app"). 11 | */ 12 | loggers: { 13 | app: { 14 | enabled: true, 15 | name: env.get('APP_NAME'), 16 | level: env.get('LOG_LEVEL'), 17 | transport: { 18 | targets: targets() 19 | .pushIf(!app.inProduction, targets.pretty()) 20 | .pushIf(app.inProduction, targets.file({ destination: 1 })) 21 | .toArray(), 22 | }, 23 | }, 24 | }, 25 | }) 26 | 27 | export default loggerConfig 28 | 29 | /** 30 | * Inferring types for the list of loggers you have configured 31 | * in your application. 32 | */ 33 | declare module '@adonisjs/core/types' { 34 | export interface LoggersList extends InferLoggers {} 35 | } 36 | -------------------------------------------------------------------------------- /config/session.ts: -------------------------------------------------------------------------------- 1 | import env from '#start/env' 2 | import app from '@adonisjs/core/services/app' 3 | import { defineConfig, stores } from '@adonisjs/session' 4 | 5 | const sessionConfig = defineConfig({ 6 | enabled: true, 7 | cookieName: 'adonis-session', 8 | 9 | /** 10 | * When set to true, the session id cookie will be deleted 11 | * once the user closes the browser. 12 | */ 13 | clearWithBrowser: false, 14 | 15 | /** 16 | * Define how long to keep the session data alive without 17 | * any activity. 18 | */ 19 | age: '2h', 20 | 21 | /** 22 | * Configuration for session cookie and the 23 | * cookie store 24 | */ 25 | cookie: { 26 | path: '/', 27 | httpOnly: true, 28 | secure: app.inProduction, 29 | sameSite: 'lax', 30 | }, 31 | 32 | /** 33 | * The store to use. Make sure to validate the environment 34 | * variable in order to infer the store name without any 35 | * errors. 36 | */ 37 | store: env.get('SESSION_DRIVER'), 38 | 39 | /** 40 | * List of configured stores. Refer documentation to see 41 | * list of available stores and their config. 42 | */ 43 | stores: { 44 | cookie: stores.cookie(), 45 | }, 46 | }) 47 | 48 | export default sessionConfig 49 | -------------------------------------------------------------------------------- /config/shield.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@adonisjs/shield' 2 | 3 | const shieldConfig = defineConfig({ 4 | /** 5 | * Configure CSP policies for your app. Refer documentation 6 | * to learn more 7 | */ 8 | csp: { 9 | enabled: false, 10 | directives: {}, 11 | reportOnly: false, 12 | }, 13 | 14 | /** 15 | * Configure CSRF protection options. Refer documentation 16 | * to learn more 17 | */ 18 | csrf: { 19 | enabled: true, 20 | exceptRoutes: [], 21 | enableXsrfCookie: false, 22 | methods: ['POST', 'PUT', 'PATCH', 'DELETE'], 23 | }, 24 | 25 | /** 26 | * Control how your website should be embedded inside 27 | * iFrames 28 | */ 29 | xFrame: { 30 | enabled: true, 31 | action: 'DENY', 32 | }, 33 | 34 | /** 35 | * Force browser to always use HTTPS 36 | */ 37 | hsts: { 38 | enabled: true, 39 | maxAge: '180 days', 40 | }, 41 | 42 | /** 43 | * Disable browsers from sniffing the content type of a 44 | * response and always rely on the "content-type" header. 45 | */ 46 | contentTypeSniffing: { 47 | enabled: true, 48 | }, 49 | }) 50 | 51 | export default shieldConfig 52 | -------------------------------------------------------------------------------- /config/static.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@adonisjs/static' 2 | 3 | /** 4 | * Configuration options to tweak the static files middleware. 5 | * The complete set of options are documented on the 6 | * official documentation website. 7 | * 8 | * https://docs.adonisjs.com/guides/basics/static-file-server 9 | */ 10 | const staticServerConfig = defineConfig({ 11 | enabled: true, 12 | etag: true, 13 | lastModified: true, 14 | dotFiles: 'ignore', 15 | }) 16 | 17 | export default staticServerConfig 18 | -------------------------------------------------------------------------------- /config/vite.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@adonisjs/vite' 2 | 3 | const viteBackendConfig = defineConfig({ 4 | /** 5 | * The output of vite will be written inside this 6 | * directory. The path should be relative from 7 | * the application root. 8 | */ 9 | buildDirectory: 'public/assets', 10 | 11 | /** 12 | * The path to the manifest file generated by the 13 | * "vite build" command. 14 | */ 15 | manifestFile: 'public/assets/.vite/manifest.json', 16 | 17 | /** 18 | * Feel free to change the value of the "assetsUrl" to 19 | * point to a CDN in production. 20 | */ 21 | assetsUrl: '/assets', 22 | 23 | scriptAttributes: { 24 | defer: true, 25 | }, 26 | }) 27 | 28 | export default viteBackendConfig 29 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import { configApp } from '@adonisjs/eslint-config' 2 | export default configApp() 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "adonisjs-web-stater-kit", 3 | "version": "0.0.0", 4 | "private": true, 5 | "type": "module", 6 | "license": "UNLICENSED", 7 | "scripts": { 8 | "start": "node bin/server.js", 9 | "build": "node ace build", 10 | "dev": "node ace serve --hmr", 11 | "test": "node ace test", 12 | "lint": "eslint .", 13 | "format": "prettier --write .", 14 | "typecheck": "tsc --noEmit" 15 | }, 16 | "imports": { 17 | "#controllers/*": "./app/controllers/*.js", 18 | "#exceptions/*": "./app/exceptions/*.js", 19 | "#models/*": "./app/models/*.js", 20 | "#mails/*": "./app/mails/*.js", 21 | "#services/*": "./app/services/*.js", 22 | "#listeners/*": "./app/listeners/*.js", 23 | "#events/*": "./app/events/*.js", 24 | "#middleware/*": "./app/middleware/*.js", 25 | "#validators/*": "./app/validators/*.js", 26 | "#providers/*": "./providers/*.js", 27 | "#policies/*": "./app/policies/*.js", 28 | "#abilities/*": "./app/abilities/*.js", 29 | "#database/*": "./database/*.js", 30 | "#tests/*": "./tests/*.js", 31 | "#start/*": "./start/*.js", 32 | "#config/*": "./config/*.js" 33 | }, 34 | "devDependencies": { 35 | "@adonisjs/assembler": "^7.8.2", 36 | "@adonisjs/eslint-config": "^2.0.0", 37 | "@adonisjs/prettier-config": "^1.4.4", 38 | "@adonisjs/tsconfig": "^1.4.0", 39 | "@japa/assert": "^4.0.1", 40 | "@japa/plugin-adonisjs": "^4.0.0", 41 | "@japa/runner": "^4.2.0", 42 | "@swc/core": "1.11.24", 43 | "@types/node": "^22.15.18", 44 | "eslint": "^9.26.0", 45 | "hot-hook": "^0.4.0", 46 | "pino-pretty": "^13.0.0", 47 | "prettier": "^3.5.3", 48 | "ts-node-maintained": "^10.9.5", 49 | "typescript": "~5.8", 50 | "vite": "^6.3.5" 51 | }, 52 | "dependencies": { 53 | "@adonisjs/auth": "^9.4.0", 54 | "@adonisjs/core": "^6.18.0", 55 | "@adonisjs/lucid": "^21.6.1", 56 | "@adonisjs/session": "^7.5.1", 57 | "@adonisjs/shield": "^8.2.0", 58 | "@adonisjs/static": "^1.1.1", 59 | "@adonisjs/vite": "^4.0.0", 60 | "@vinejs/vine": "^3.0.1", 61 | "edge.js": "^6.2.1", 62 | "reflect-metadata": "^0.2.2" 63 | }, 64 | "hotHook": { 65 | "boundaries": [ 66 | "./app/controllers/**/*.ts", 67 | "./app/middleware/*.ts" 68 | ] 69 | }, 70 | "prettier": "@adonisjs/prettier-config" 71 | } 72 | -------------------------------------------------------------------------------- /resources/css/app.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | html, 7 | body { 8 | height: 100%; 9 | width: 100%; 10 | } 11 | -------------------------------------------------------------------------------- /resources/js/app.js: -------------------------------------------------------------------------------- 1 | console.log('Hello World') 2 | -------------------------------------------------------------------------------- /resources/views/pages/errors/not_found.edge: -------------------------------------------------------------------------------- 1 |

2 | 404 - Page not found 3 |

4 |

5 | This template is rendered by the 6 | status pages feature 7 | of the global exception handler. 8 |

9 | -------------------------------------------------------------------------------- /resources/views/pages/errors/server_error.edge: -------------------------------------------------------------------------------- 1 |

2 | {{ error.code }} - Server error 3 |

4 |

5 | This template is rendered by the 6 | status pages feature 7 | of the global exception handler. 8 |

9 | -------------------------------------------------------------------------------- /resources/views/pages/home.edge: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | AdonisJS - A fully featured web framework for Node.js 8 | 9 | 10 | 11 | 15 | 16 | 32 | 33 | 34 | 35 | 66 | 67 | @vite(['resources/css/app.css', 'resources/js/app.js']) 68 | @stack('dumper') 69 | 70 | 71 |
74 |
75 | 76 |
77 | {{-- Header --}} 78 | 89 | 90 | {{-- Bento with documentation, Adocasts, packages and Discord --}} 91 |
94 |
97 |
98 | 99 | 100 | 101 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 |
126 |
127 |
128 | 129 |
130 |
131 | 132 | 136 | 137 |
138 | 139 |
140 |

141 | 142 | Documentation 143 | 144 | 145 |

146 | 147 |

148 | Dive into the official documentation to learn AdonisJS. Read carefully to discover an unmatched set of features, best practices and developer experience. Through examples, guides and API references, you'll find everything you need to build your next project. From installation to deployment, we've got you covered. 149 |

150 |
151 |
152 |
153 | 154 |
157 |
158 | 159 | 163 | 164 |
165 | 166 |
167 |

168 | 169 | Adocasts 170 | 171 | 172 |

173 | 174 |

175 | Level up your development and Adonis skills with hours of video content, from beginner to advanced, through databases, testing, and more. 176 |

177 |
178 |
179 | 180 |
183 |
184 | 185 | 189 | 190 |
191 | 192 |
193 |

194 | 195 | Packages 196 | 197 | 198 |

199 | 200 |

201 | Supercharge your AdonisJS application with packages built and maintained by both the core team and the community. 202 |

203 |
204 |
205 | 206 |
209 |
210 | 211 | 215 | 216 |
217 | 218 |
219 |

220 | 221 | Discord 222 | 223 | 224 |

225 | 226 |

227 | Never get lost again, ask questions, and share your knowledge or projects with a growing and supportive community. Join us. 228 |

229 |
230 |
231 |
232 | 233 | {{-- Features --}} 234 |
235 |
236 | 279 | 280 | 320 | 321 | 361 | 362 | 398 |
399 |
400 | 401 |
404 | Route for this page is registered in start/routes.ts file, rendering resources/views/pages/home.edge template 405 |
406 |
407 | 408 | 409 | -------------------------------------------------------------------------------- /start/env.ts: -------------------------------------------------------------------------------- 1 | /* 2 | |-------------------------------------------------------------------------- 3 | | Environment variables service 4 | |-------------------------------------------------------------------------- 5 | | 6 | | The `Env.create` method creates an instance of the Env service. The 7 | | service validates the environment variables and also cast values 8 | | to JavaScript data types. 9 | | 10 | */ 11 | 12 | import { Env } from '@adonisjs/core/env' 13 | 14 | export default await Env.create(new URL('../', import.meta.url), { 15 | NODE_ENV: Env.schema.enum(['development', 'production', 'test'] as const), 16 | PORT: Env.schema.number(), 17 | APP_KEY: Env.schema.string(), 18 | HOST: Env.schema.string({ format: 'host' }), 19 | LOG_LEVEL: Env.schema.string(), 20 | 21 | /* 22 | |---------------------------------------------------------- 23 | | Variables for configuring session package 24 | |---------------------------------------------------------- 25 | */ 26 | SESSION_DRIVER: Env.schema.enum(['cookie', 'memory'] as const), 27 | }) 28 | -------------------------------------------------------------------------------- /start/kernel.ts: -------------------------------------------------------------------------------- 1 | /* 2 | |-------------------------------------------------------------------------- 3 | | HTTP kernel file 4 | |-------------------------------------------------------------------------- 5 | | 6 | | The HTTP kernel file is used to register the middleware with the server 7 | | or the router. 8 | | 9 | */ 10 | 11 | import router from '@adonisjs/core/services/router' 12 | import server from '@adonisjs/core/services/server' 13 | 14 | /** 15 | * The error handler is used to convert an exception 16 | * to an HTTP response. 17 | */ 18 | server.errorHandler(() => import('#exceptions/handler')) 19 | 20 | /** 21 | * The server middleware stack runs middleware on all the HTTP 22 | * requests, even if there is no route registered for 23 | * the request URL. 24 | */ 25 | server.use([ 26 | () => import('#middleware/container_bindings_middleware'), 27 | () => import('@adonisjs/static/static_middleware'), 28 | () => import('@adonisjs/vite/vite_middleware'), 29 | ]) 30 | 31 | /** 32 | * The router middleware stack runs middleware on all the HTTP 33 | * requests with a registered route. 34 | */ 35 | router.use([ 36 | () => import('@adonisjs/core/bodyparser_middleware'), 37 | () => import('@adonisjs/session/session_middleware'), 38 | () => import('@adonisjs/shield/shield_middleware'), 39 | ]) 40 | 41 | /** 42 | * Named middleware collection must be explicitly assigned to 43 | * the routes or the routes group. 44 | */ 45 | export const middleware = router.named({}) 46 | -------------------------------------------------------------------------------- /start/routes.ts: -------------------------------------------------------------------------------- 1 | /* 2 | |-------------------------------------------------------------------------- 3 | | Routes file 4 | |-------------------------------------------------------------------------- 5 | | 6 | | The routes file is used for defining the HTTP routes. 7 | | 8 | */ 9 | 10 | import router from '@adonisjs/core/services/router' 11 | 12 | router.on('/').render('pages/home') 13 | -------------------------------------------------------------------------------- /tests/bootstrap.ts: -------------------------------------------------------------------------------- 1 | import { assert } from '@japa/assert' 2 | import app from '@adonisjs/core/services/app' 3 | import type { Config } from '@japa/runner/types' 4 | import { pluginAdonisJS } from '@japa/plugin-adonisjs' 5 | import testUtils from '@adonisjs/core/services/test_utils' 6 | 7 | /** 8 | * This file is imported by the "bin/test.ts" entrypoint file 9 | */ 10 | 11 | /** 12 | * Configure Japa plugins in the plugins array. 13 | * Learn more - https://japa.dev/docs/runner-config#plugins-optional 14 | */ 15 | export const plugins: Config['plugins'] = [assert(), pluginAdonisJS(app)] 16 | 17 | /** 18 | * Configure lifecycle function to run before and after all the 19 | * tests. 20 | * 21 | * The setup functions are executed before all the tests 22 | * The teardown functions are executed after all the tests 23 | */ 24 | export const runnerHooks: Required> = { 25 | setup: [], 26 | teardown: [], 27 | } 28 | 29 | /** 30 | * Configure suites by tapping into the test suite instance. 31 | * Learn more - https://japa.dev/docs/test-suites#lifecycle-hooks 32 | */ 33 | export const configureSuite: Config['configureSuite'] = (suite) => { 34 | if (['browser', 'functional', 'e2e'].includes(suite.name)) { 35 | return suite.setup(() => testUtils.httpServer().start()) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@adonisjs/tsconfig/tsconfig.app.json", 3 | "compilerOptions": { 4 | "rootDir": "./", 5 | "outDir": "./build" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import adonisjs from '@adonisjs/vite/client' 3 | 4 | export default defineConfig({ 5 | plugins: [ 6 | adonisjs({ 7 | /** 8 | * Entrypoints of your application. Each entrypoint will 9 | * result in a separate bundle. 10 | */ 11 | entrypoints: ['resources/css/app.css', 'resources/js/app.js'], 12 | 13 | /** 14 | * Paths to watch and reload the browser on file change 15 | */ 16 | reload: ['resources/views/**/*.edge'], 17 | }), 18 | ], 19 | }) 20 | --------------------------------------------------------------------------------