DevGang
Авторизоваться

Ваш первый API с Bun, Express и Prisma

Вы уже увидели новую, классную и быструю среду разработки для JavaScript и задаетесь вопросом, как начать разрабатывать веб-приложения? Возможно, эта статья поможет вам. Мне нравится видеть новые способы создания приложений, которые привносят инновации в экосистему JS, а Bun привносит в нее нечто большее. Здесь, без дополнительных библиотек, вы можете создать свой API, протестировать его, собрать в пакет и даже использовать собственную интеграцию SQLite, и все это в быстрой и простой в использовании среде выполнения. В ней даже уже есть некоторые фреймворки, но это - наработки на будущее.

Установка и Hello World

Прежде всего, загрузите и установите bun с помощью curl, как указано в файле bun.sh

☁ ~ curl -fsSL 'https://bun.sh/install' | bash
######################################################################## 100,0%
bun was installed successfully to ~/.bun/bin/bun 
Run 'bun --help' to get started

Затем создайте папку, в которой будет находиться ваш проект, перейдите в нее по cd и выполните команду bun init, в результате чего будет создан новый проект, в котором вам нужно будет выбрать имя проекта и точку входа. По умолчанию cli будет использовать имя вашей папки и стартовать с index.ts.

☁  projects  mkdir bunApp  
☁  projects  cd bunApp  
☁  bunApp  bun init    
bun init helps you get started with a minimal project and tries to guess sensible defaults. Press ^C anytime to quit

package name (bunapp): 
entry point (index.ts): 

Done! A package.json file was saved in the current directory.
 + index.ts
 + .gitignore
 + tsconfig.json (for editor auto-complete)
 + README.md

To get started, run:
  bun run index.ts
☁  bunApp  

После этого откройте ваш любимый ide (здесь я использую vscode), и вы увидите очень скромное содержимое, с некоторыми конфигурационными файлами и index.ts, содержащим наш Hello World!!!

Почти все эти файлы являются общими для всех репозиториев, но есть один, который называется bun.lockb, это автоматически генерируемый файл, похожий на другие .lock-файлы, и сейчас он не так важен, но вы можете узнать о нем в документации по Bun.

Мы уже можем запустить наш файл index.ts, чтобы начать наш маленький проект:

☁  bunApp  bun index.ts
Hello via Bun!
☁  bunApp  

Прежде чем мы перейдем к следующей теме, необходимо сделать еще одну вещь. Если вы знакомы с Node, то наверняка использовали Nodemon для мониторинга проекта и перезагрузки при изменении кода. Bun просто использует тег --watch для работы в этом режиме, поэтому вам не нужен внешний модуль.

Давайте добавим в наш package.json два скрипта, один для запуска проекта, другой для режима разработчика, включая тег --watch.

{
    "name": "bunapp",
    "module": "index.ts",
    "type": "module",
    "scripts": {
        "start": "bun run index.ts",
        "dev": "bun --watch run index.ts"
    },
    "devDependencies": {
        "bun-types": "latest"
    },
    "peerDependencies": {
        "typescript": "^5.0.0"
    },
}

Маршруты

Для инициализации нашего сервера мы просто используем Bun.serve(). Он может получать некоторые параметры, но сейчас нам нужен только порт для доступа к нашему приложению и обработчик fetch(), который мы используем для обработки наших запросов. Напишите приведенный ниже код и запустите наш скрипт bun run dev.

const server = Bun.serve({
    port: 8080,
    fetch(req) {
        return new Response("Bun!")
    }
})

console.log(`Listening on ${server.hostname}: ${server.port}...`)

Это наш первый http-запрос. Так как мы не указали метод, то на любой запрос к нашей конечной точке, которая должна быть localhost:8080, он вернет ответ Bun! С этим мы разберемся в следующей теме, а пока просто добавим еще немного кода, следуя примеру из документации, для составления наших маршрутов.

const server = Bun.serve({
    port: 8080,
    fetch(req) {
        const url = new URL(req.url)
        if(url.pathname === "/") return new Response("Home Page!")
        if(url.pathname === "/blog") return new Response("Blog!")
        return new Response("404!")
    }
})

console.log(`Listening on ${server.hostname}: ${server.port}...`)

Он берет наш url из объекта запроса и разбирает его до объекта URL, используя API Node. Это происходит потому, что Bun стремится к полной совместимости с Node, поэтому большинство библиотек и пакетов, используемых на Node, работает на Bun изначально.

HTTP-запросы

Если хотите, используйте console.log(req) для просмотра объекта нашего запроса, он выглядит следующим образом:

Listening on localhost: 8080...
Request (0 KB) {
  method: "GET",
  url: "http://localhost:8080/",
  headers: Headers {
    "host": "localhost:8080",
    "connection": "keep-alive",
    "upgrade-insecure-requests": "1",
    "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36",
    "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
    "accept-language": "en-US,en",
    "sec-fetch-mode": "navigate",
    "sec-fetch-dest": "document",
    "accept-encoding": "gzip, deflate, br",
    "if-none-match": "W/\"b-f4FzwVt2eK0ePdTZJcUnF/0T+Zw\"",
    "sec-ch-ua": "\"Brave\";v=\"117\", \"Not;A=Brand\";v=\"8\", \"Chromium\";v=\"117\"",
    "sec-ch-ua-mobile": "?0",
    "sec-ch-ua-platform": "\"Linux\"",
    "sec-gpc": "1",
    "sec-fetch-site": "none",
    "sec-fetch-user": "?1"
  }
}

Дело в том, что мы можем использовать множество условий для проверки метода и/или конечной точки. Это становится мучительно грязным и не очень приятным для чтения.

const server = Bun.serve({
    port: 8080,
    fetch(req) {
        const url = new URL(req.url)
        if(url.pathname === "/") return new Response("Home Page!")
        if(url.pathname === "/blog") {
        switch (req.method) {
            case "GET":
            // handle with GET
            case "POST":
            // handle with POST
            case "PUT":
            // handle with PUT
            case "DELETE":
            // handle with DELETE
            }
            // any other routes and methods...  
        }
        return new Response("404!")
    }
})

console.log(`Listening on ${server.hostname}: ${server.port}...`)const server = Bun.serve({
    port: 8080,
    fetch(req) {
        const url = new URL(req.url)
        if(url.pathname === "/") return new Response("Home Page!")
        if(url.pathname === "/blog") {
        switch (req.method) {
            case "GET":
            // handle with GET
            case "POST":
            // handle with POST
            case "PUT":
            // handle with PUT
            case "DELETE":
            // handle with DELETE
            }
            // any other routes and methods...  
        }
        return new Response("404!")
    }
})

console.log(`Listening on ${server.hostname}: ${server.port}...`)const server = Bun.serve({
    port: 8080,
    fetch(req) {
        const url = new URL(req.url)
        if(url.pathname === "/") return new Response("Home Page!")
        if(url.pathname === "/blog") {
        switch (req.method) {
            case "GET":
            // handle with GET
            case "POST":
            // handle with POST
            case "PUT":
            // handle with PUT
            case "DELETE":
            // handle with DELETE
            }
            // any other routes and methods...  
        }
        return new Response("404!")
    }
})

console.log(`Listening on ${server.hostname}: ${server.port}...`)const server = Bun.serve({
    port: 8080,
    fetch(req) {
        const url = new URL(req.url)
        if(url.pathname === "/") return new Response("Home Page!")
        if(url.pathname === "/blog") {
        switch (req.method) {
            case "GET":
            // handle with GET
            case "POST":
            // handle with POST
            case "PUT":
            // handle with PUT
            case "DELETE":
            // handle with DELETE
            }
            // any other routes and methods...  
        }
        return new Response("404!")
    }
})

console.log(`Listening on ${server.hostname}: ${server.port}...`)const server = Bun.serve({
    port: 8080,
    fetch(req) {
        const url = new URL(req.url)
        if(url.pathname === "/") return new Response("Home Page!")
        if(url.pathname === "/blog") {
        switch (req.method) {
            case "GET":
            // handle with GET
            case "POST":
            // handle with POST
            case "PUT":
            // handle with PUT
            case "DELETE":
            // handle with DELETE
            }
            // any other routes and methods...  
        }
        return new Response("404!")
    }
})

console.log(`Listening on ${server.hostname}: ${server.port}...`)const server = Bun.serve({
    port: 8080,
    fetch(req) {
        const url = new URL(req.url)
        if(url.pathname === "/") return new Response("Home Page!")
        if(url.pathname === "/blog") {
        switch (req.method) {
            case "GET":
            // handle with GET
            case "POST":
            // handle with POST
            case "PUT":
            // handle with PUT
            case "DELETE":
            // handle with DELETE
            }
            // any other routes and methods...  
        }
        return new Response("404!")
    }
})

console.log(`Listening on ${server.hostname}: ${server.port}...`)const server = Bun.serve({
    port: 8080,
    fetch(req) {
        const url = new URL(req.url)
        if(url.pathname === "/") return new Response("Home Page!")
        if(url.pathname === "/blog") {
        switch (req.method) {
            case "GET":
            // handle with GET
            case "POST":
            // handle with POST
            case "PUT":
            // handle with PUT
            case "DELETE":
            // handle with DELETE
            }
            // any other routes and methods...  
        }
        return new Response("404!")
    }
})

console.log(`Listening on ${server.hostname}: ${server.port}...`)const server = Bun.serve({
    port: 8080,
    fetch(req) {
        const url = new URL(req.url)
        if(url.pathname === "/") return new Response("Home Page!")
        if(url.pathname === "/blog") {
        switch (req.method) {
            case "GET":
            // handle with GET
            case "POST":
            // handle with POST
            case "PUT":
            // handle with PUT
            case "DELETE":
            // handle with DELETE
            }
            // any other routes and methods...  
        }
        return new Response("404!")
    }
})

console.log(`Listening on ${server.hostname}: ${server.port}...`)

Помните, что большинство пакетов Node работает и на Bun? Давайте воспользуемся Express для облегчения процесса разработки, подробности можно посмотреть в документации по Bun. Начнем с того, что остановим наше приложение с помощью CTRL + C и выполним команду bun add express.

Listening on localhost: 8080...
^C
☁  bunApp  bun add express
bun add v1.0.2 (37edd5a6)

 installed express@4.18.2


 58 packages installed [1200.00ms]
☁  bunApp 

Также перепишем наш index.ts, используя шаблон Express с некоторыми маршрутами.

import express, { Request, Response } from "express";

const app = express();
const port = 8080;
app.use(express.json());

app.post("/blog", (req: Request, res: Response) => {
//create new blog post
});

app.get("/", (req: Request, res: Response) => {
res.send("Api running");
});

app.get("/blog", (req: Request, res: Response) => {
//get all posts
});

app.get("/blog/:post", (req: Request, res: Response) => {
//get a specific post
});

app.delete("/blog/:post", (req: Request, res: Response) => {
//delete a post
});

app.listen(port, () => {
console.log(`Listening on port ${port}...`);
});

Добавление базы данных

И последнее, что необходимо сделать для нашего CRUD, - это реализовать соединения с базой данных. В Bun уже есть собственный драйвер SQLite3, но мы используем Prisma, так как работать с ORM проще. Давайте последуем указаниям из документации Bun и начнем с добавления Prisma с помощью bun add prisma и инициализации ее с помощью bunx prisma init --datasource-provider sqlite. Затем перейдем к нашему новому файлу schema.prisma и вставим новую модель.

☁  bunApp  bun add prisma                               
bun add v1.0.2 (37edd5a6)

 installed prisma@5.3.1 with binaries:
  - prisma


 2 packages installed [113.00ms]
☁  bunApp  bunx prisma init --datasource-provider sqlite

✔ Your Prisma schema was created at prisma/schema.prisma
  You can now open it in your favorite editor.

warn You already have a .gitignore file. Don't forget to add `.env` in it to not commit any private information.

Next steps:
1. Set the DATABASE_URL in the .env file to point to your existing database. If your database has no tables yet, read https://pris.ly/d/getting-started
2. Run prisma db pull to turn your database schema into a Prisma schema.
3. Run prisma generate to generate the Prisma Client. You can then start querying your database.

More information in our documentation:
https://pris.ly/d/getting-started

☁  bunApp  

После этого выполните команду bunx prisma generate, а затем bunx prisma migrate dev --name init. Теперь у нас есть все необходимое для нашего маленького API. Вернитесь к нашему index.ts, импортируйте и инициализируйте клиент Prisma, после чего мы готовы завершить работу над маршрутами.

import { PrismaClient } from "@prisma/client";

/* Config database */
const prisma = new PrismaClient();

Итоговый файл index.ts в конечном итоге должен выглядеть следующим образом:

import express, { Request, Response } from "express";
import { PrismaClient } from "@prisma/client";

/* Config database */
const prisma = new PrismaClient();

/* Config server */
const app = express();
const port = 8080;
app.use(express.json());

app.post("/blog", async (req: Request, res: Response) => {
    try {
        const { title, content } = req.body;

        await prisma.post.create({
            data: {
            title: title,
            content: content,
            },
        });
        res.status(201).json({ message: `Post created!` });
    } catch (error) {
        console.error(`Something went wrong while create a new post: `, error);
    }
});

app.get("/", (req: Request, res: Response) => {
    res.send("Api running");
});

app.get("/blog", async (req: Request, res: Response) => {
    try {
        const posts = await prisma.post.findMany();
        res.json(posts);
    } catch (error) {
        console.error(`Something went wrong while fetching all posts: `, error);
    }
});

app.get("/blog/:postId", async (req: Request, res: Response) => {
    try {
        const postId = parseInt(req.params.postId, 10);

        const post = await prisma.post.findUnique({
            where: {
                id: postId,
            },
        });
        if (!post) res.status(404).json({ message: "Post not found" });
        res.json(post);
    } catch (error) {
        console.error(`Something went wrong while fetching the post: `, error);
    }
});

app.delete("/blog/:postId", async (req: Request, res: Response) => {
    try {
        const postId = parseInt(req.params.postId, 10);
        await prisma.post.delete({
            where: {
            id: postId,
            },
        });
        res.send(`Post deleted!`);
    } catch (error) {
        return res.status(404).json({ message: "Post not found" });
    }
});

app.listen(port, () => {
    console.log(`Listening on port ${port}...`);
});

Заключение

Наконец-то наш API готов! Дальше можно реализовать множество других вещей, таких как: добавление новых моделей, создание фронтенда и, конечно же, написание тестов (попробуем сделать это дальше). Вы видите, что Bun обеспечивает определенное качество жизни и при этом совместим с большинством пакетов Node, облегчая жизнь нашим разработчикам.

Источник:

#Express
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу