Node.js和TypeScript教程:使用Typescript、NodeJS和基于文件的存储系统构建REST API
欢迎到我的博客!在本教程中,我将指导您完成使用 Node.js、Express 和 TypeScript 构建强大的微型电子商务 API 的过程。我们将共同探索各种功能和技术,使您能够为电子商务应用程序创建强大的 API。
我们在这个项目中的关键决策之一是实现基于文件的存储系统,而不是依赖 MongoDB 等传统数据库。这种方法简单且易于实施,非常适合小型应用程序或可能不需要成熟的数据库管理系统的场景。
本教程将涵盖用户管理、产品处理和身份验证等基本主题。
(更|多优质内|容:java567 点 c0m)
您将获得使用涵盖用户和产品数据的功能的实践经验,演示这些实体如何在电子商务 API 中进行交互。在本教程结束时,您将全面了解如何构建强大的 API,以实现与用户和产品资源的无缝交互。
因此,请与我一起踏上这个激动人心的旅程,我们将深入研究使用 Node.js、Express 和 TypeScript 创建微型电子商务 API。
在 Node.js 中开始使用 TypeScript 首先创建一个如下所示的项目目录。
接下来,使用以下命令创建具有默认设置的 package.json 文件,在项目目录中初始化 Node.js 项目:
npm init -y
安装项目依赖项
您的 Node.js 项目需要几个依赖项才能使用 TypeScript 创建安全的 Express 服务器。像这样安装它们:
npm i express dotenv helmet cors http-status-codes uuid bcryptjs 要使用 TypeScript,您还需要安装稳定版本的 TypeScript 作为开发人员依赖项:
npm i -D typescript
要有效地使用 TypeScript,您需要为之前安装的包安装类型定义:
npm i -D @types/express @types/dotenv @types/helmet @types/cors @types/http-status-codes @types/uuid @types/bcryptjs
使用以下变量填充 .env 隐藏文件,该变量定义服务器可用于侦听请求的端口:
PORT=7000
接下来,找到 src 文件夹根目录中的 app.js 文件,导入之前安装的项目依赖项,并使用 dotenv.config() 方法从本地 .env 文件加载任何环境变量:
import express from "express"
import * as dotevnv from "dotenv"
import cors from "cors"
import helmet from "helmet"
dotevnv.config()
if (!process.env.PORT) {
console.log(`No port value specified...`)
}
const PORT = parseInt(process.env.PORT as string, 10)
const app = express()
app.use(express.json())
app.use(express.urlencoded({extended : true}))
app.use(cors())
app.use(helmet())
app.listen(PORT, () => {
console.log(`Server is listening on port ${PORT}`)
})
在此代码片段中,使用 Express 框架设置 Node.js 应用程序。以下是所发生情况的详细说明:
导入所需的模块:
Express被导入作为构建 Web 应用程序的主要框架。
导入dotenv来处理环境变量。
导入cors是为了实现跨源资源共享。
导入头盔是为了向 HTTP 响应添加安全标头。
该代码检查 PORT 环境变量是否已定义。如果没有,一条消息会记录到控制台。
使用 parseInt() 将 PORT 变量从字符串解析为整数。
Express 应用程序的实例是使用express() 创建的,并分配给app 变量。
Express应用程序中添加了中间件功能:
express.json()用于解析传入请求的 JSON 正文。
express.urlencoded({extended : true})用于解析传入请求的 URL 编码正文。
cors()用于启用跨源资源共享。
头盔()用于通过设置各种HTTP标头来增强应用程序的安全性。
Express 应用程序通过调用 app.listen() 开始侦听指定的端口。服务器运行后,一条指示端口号的消息将记录到控制台。
改进 TypeScript 开发工作流程
TypeScript 编译过程会增加应用程序的引导时间。但是,只要源代码发生更改,您就不需要重新编译整个项目。您可以设置 ts-node-dev 以显着减少进行更改时重新启动应用程序所需的时间。
首先安装此软件包以增强您的开发工作流程:
npm i -D ts-node-dev
当任何所需文件发生更改时,ts-node-dev 会重新启动目标 Node.js 进程。但是,它在重新启动之间共享 Typescript 编译过程,这可以显着提高重新启动速度。
您可以在 package.json 中创建 dev npm 脚本来运行服务器。像这样更新你的 package.json 文件。
{"name": "typescript-nodejs","version": "1.0.0","description": "","main": "index.js","scripts": {"test": "echo \"Error: no test specified\" && exit 1","dev": "ts-node-dev --pretty --respawn ./src/app.ts"},"keywords": [],"author": "","license": "ISC","dependencies": {"@types/nanoid": "^3.0.0","@types/uuid": "^9.0.2","bcryptjs": "^2.4.3","cors": "^2.8.5","dotenv": "^16.3.0","express": "^4.18.2","helmet": "^7.0.0","http-status-codes": "^2.2.0","nanoid": "^4.0.2","uuid": "^9.0.0"},"devDependencies": {"@types/bcryptjs": "^2.4.2","@types/cors": "^2.8.13","@types/dotenv": "^8.2.0","@types/express": "^4.17.17","@types/helmet": "^4.0.0","@types/http-status-codes": "^1.2.0","ts-node-dev": "^2.0.0"}}
让我们简单地分解一下 ts-node-dev 采用的选项:
--respawn:脚本退出后继续观察变化。
--pretty:使用漂亮的诊断格式化程序(TS_NODE_PRETTY)。
./src/app.ts:这是应用程序的入口文件。
现在,只需运行开发脚本即可启动您的项目:
npm run dev
如果一切正常,您将看到一条消息,指示服务器正在侦听端口 7000 上的请求。
使用 TypeScript 接口对数据进行建模
在创建任何路由之前,定义要管理的数据的结构。我们的用户数据库将具有以下属性:
id:(字符串)项目记录的唯一标识符。 用户名:(字符串)项目的名称。 电子邮件:(数字)商品价格(以美分为单位)。 密码:(字符串)项目的描述。
使用以下定义填充 src/users/user.interface.ts:
export interface User {
username : string,
email : string,
password : string
}
export interface UnitUser extends User {
id : string
}
export interface Users {
[key : string] : UnitUser
}
此代码定义了三个 TypeScript 接口:
用户界面代表具有三个属性的基本用户对象:
username,这是表示用户的用户名的字符串。 email,它是表示用户的电子邮件地址的字符串。 password,它是表示用户密码的字符串。
UnitUser 接口扩展了 User 接口并添加了 id 属性:
id,是一个字符串,代表用户的唯一标识符。
Users 接口表示具有动态键的用户对象的集合:
[key: string]表示Users对象的键可以是任意字符串。 Users 对象的值属于 UnitUser 类型,这意味着集合中的每个用户对象都应符合 UnitUser 接口。 简单来说,这些接口定义了用户对象的结构和类型。User 接口定义了用户的基本属性,而 UnitUser 接口添加了 id 属性来表示具有唯一标识符的用户。Users 接口表示用户对象的集合,其中键是字符串,值是 UnitUser 对象。
接下来,我们将为数据存储创建逻辑。如果您愿意,您可以将其称为数据库。 使用以下代码填充 src/users/user.database.ts:
import { User, UnitUser, Users } from "./user.interface";
import bcrypt from "bcryptjs"
import {v4 as random} from "uuid"
import fs from "fs"
let users: Users = loadUsers()
function loadUsers () : Users {
try {
const data = fs.readFileSync("./users.json", "utf-8")
return JSON.parse(data)
} catch (error) {
console.log(`Error ${error}`)
return {}
}
}
function saveUsers () {
try {
fs.writeFileSync("./users.json", JSON.stringify(users), "utf-8")
console.log(`User saved successfully!`)
} catch (error) {
console.log(`Error : ${error}`)
}
}
export const findAll = async (): Promise<UnitUser[]> => Object.values(users);
export const findOne = async (id: string): Promise<UnitUser> => users[id];
export const create = async (userData: UnitUser): Promise<UnitUser | null> => {
let id = random()
let check_user = await findOne(id);
while (check_user) {
id = random()
check_user = await findOne(id)
}
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(userData.password, salt);
const user : UnitUser = {
id : id,
username : userData.username,
email : userData.email,
password: hashedPassword
};
users[id] = user;
saveUsers()
return user;
};
export const findByEmail = async (user_email: string): Promise<null | UnitUser> => {
const allUsers = await findAll();
const getUser = allUsers.find(result => user_email === result.email);
if (!getUser) {
return null;
}
return getUser;
};
export const comparePassword = async (email : string, supplied_password : string) : Promise<null | UnitUser> => {
const user = await findByEmail(email)
const decryptPassword = await bcrypt.compare(supplied_password, user!.password)
if (!decryptPassword) {
return null
}
return user
}
export const update = async (id : string, updateValues : User) : Promise<UnitUser | null> => {
const userExists = await findOne(id)
if (!userExists) {
return null
}
if(updateValues.password) {
const salt = await bcrypt.genSalt(10)
const newPass = await bcrypt.hash(updateValues.password, salt)
updateValues.password = newPass
}
users[id] = {
...userExists,
...updateValues
}
saveUsers()
return users[id]
}
export const remove = async (id : string) : Promise<null | void> => {
const user = await findOne(id)
if (!user) {
return null
}
delete users[id]
saveUsers()
}
让我解释一下上面代码中的每个函数:
loadUsers:此函数使用 fs 模块从名为“users.json”的文件中读取数据。它尝试将数据解析为 JSON 并将其作为用户对象返回。如果在此过程中发生错误,它会记录错误并返回一个空对象。
saveUsers:此函数通过使用 fs 模块的 writeFileSync 方法写入用户对象的 JSON 字符串表示形式,将用户对象保存到“users.json”文件中。如果在此过程中发生错误,则会记录该错误。
findAll:此函数返回一个解析为 UnitUser 对象数组的承诺。它使用 Object.values(users) 从用户对象中提取值(用户)。
findOne:此函数采用 id 参数并返回一个 Promise,该 Promise 解析为与 users 对象中的该 id 对应的 UnitUser 对象。
create:此函数将 userData 对象作为输入,并返回一个解析为新创建的 UnitUser 对象的 Promise。它使用 uuid 包生成一个随机 ID,并检查具有该 ID 的用户是否已存在。如果具有该 id 的用户存在,它将生成一个新的 id,直到找到唯一的 id。然后,它使用 bcrypt 对 userData 对象的密码进行哈希处理,并将哈希后的密码保存在 UnitUser 对象中。UnitUser 对象被添加到 users 对象中,使用 saveUsers 保存并返回。
findByEmail:此函数采用 user_email 参数,并返回一个承诺,如果具有指定电子邮件的用户存在,则该承诺解析为 UnitUser 对象,否则返回 null。它使用 findAll 检索所有用户,并使用 find 方法查找具有匹配电子邮件地址的用户。
ComparePassword:此函数采用电子邮件和提供的密码作为参数,如果提供的密码与用户存储的密码匹配,则返回一个解析为 UnitUser 对象的承诺,否则返回 null。它调用 findByEmail 通过电子邮件检索用户,然后使用 bcrypt.compare 将散列存储的密码与提供的密码进行比较。
update:此函数采用 id 和 updateValues 作为参数,并返回一个承诺,如果具有指定 id 的用户存在,则该承诺解析为更新的 UnitUser 对象。它使用 findOne 检查用户是否存在,如果 updateValues 包含新密码,则更新用户的密码。用户的属性使用 updateValues 中的值进行更新,并使用 saveUsers 保存用户对象。
remove:此函数采用 id 参数并返回一个承诺,如果具有指定 id 的用户不存在,则该承诺解析为 null,否则返回 void。它使用 findOne 检查用户是否存在,并使用 delete 关键字从 users 对象中删除该用户。然后使用 saveUsers 保存更新的用户对象。
这些函数充当我们的 API 可用来处理和检索数据库信息的方法。
接下来,让 all 将所有必需的函数和模块导入到路由文件 ./src/users.routes.ts 中并填充如下:
import express, {Request, Response} from "express"
import { UnitUser, User } from "./user.interface"
import {StatusCodes} from "http-status-codes"
import * as database from "./user.database"
export const userRouter = express.Router()
userRouter.get("/users", async (req : Request, res : Response) => {
try {
const allUsers : UnitUser[] = await database.findAll()
if (!allUsers) {
return res.status(StatusCodes.NOT_FOUND).json({msg : `No users at this time..`})
}
return res.status(StatusCodes.OK).json({total_user : allUsers.length, allUsers})
} catch (error) {
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({error})
}
})
userRouter.get("/user/:id", async (req : Request, res : Response) => {
try {
const user : UnitUser = await database.findOne(req.params.id)
if (!user) {
return res.status(StatusCodes.NOT_FOUND).json({error : `User not found!`})
}
return res.status(StatusCodes.OK).json({user})
} catch (error) {
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({error})
}
})
userRouter.post("/register", async (req : Request, res : Response) => {
try {
const { username, email, password } = req.body
if (!username || !email || !password) {
return res.status(StatusCodes.BAD_REQUEST).json({error : `Please provide all the required parameters..`})
}
const user = await database.findByEmail(email)
if (user) {
return res.status(StatusCodes.BAD_REQUEST).json({error : `This email has already been registered..`})
}
const newUser = await database.create(req.body)
return res.status(StatusCodes.CREATED).json({newUser})
} catch (error) {
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({error})
}
})
userRouter.post("/login", async (req : Request, res : Response) => {
try {
const {email, password} = req.body
if (!email || !password) {
return res.status(StatusCodes.BAD_REQUEST).json({error : "Please provide all the required parameters.."})
}
const user = await database.findByEmail(email)
if (!user) {
return res.status(StatusCodes.NOT_FOUND).json({error : "No user exists with the email provided.."})
}
const comparePassword = await database.comparePassword(email, password)
if (!comparePassword) {
return res.status(StatusCodes.BAD_REQUEST).json({error : `Incorrect Password!`})
}
return res.status(StatusCodes.OK).json({user})
} catch (error) {
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({error})
}
})
userRouter.put('/user/:id', async (req : Request, res : Response) => {
try {
const {username, email, password} = req.body
const getUser = await database.findOne(req.params.id)
if (!username || !email || !password) {
return res.status(401).json({error : `Please provide all the required parameters..`})
}
if (!getUser) {
return res.status(404).json({error : `No user with id ${req.params.id}`})
}
const updateUser = await database.update((req.params.id), req.body)
return res.status(201).json({updateUser})
} catch (error) {
console.log(error)
return res.status(500).json({error})
}
})
userRouter.delete("/user/:id", async (req : Request, res : Response) => {
try {
const id = (req.params.id)
const user = await database.findOne(id)
if (!user) {
return res.status(StatusCodes.NOT_FOUND).json({error : `User does not exist`})
}
await database.remove(id)
return res.status(StatusCodes.OK).json({msg : "User deleted"})
} catch (error) {
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({error})
}
})
这是每个函数的作用:
userRouter.get("/users"):此函数处理对“/users”的 GET 请求。它从数据库模块调用 findAll 函数来检索所有用户。如果未找到用户,它将返回 404 状态代码和一条消息。如果找到用户,则返回 200 状态码以及用户总数和所有用户的数组。
userRouter.get("/user/:id"):此函数处理对“/user/:id”的 GET 请求,其中 :id 代表特定用户的 ID。它从数据库模块调用 findOne 函数来检索具有指定 ID 的用户。如果未找到用户,则会返回 404 状态代码和错误消息。如果找到用户,它将返回 200 状态代码以及用户对象。
userRouter.post("/register"):此函数处理对“/register”的 POST 请求以进行用户注册。它从请求正文中提取用户名、电子邮件和密码。如果缺少任何这些字段,它将返回 400 状态代码和错误消息。它从数据库模块调用 findByEmail 函数来检查电子邮件是否已注册。如果找到电子邮件,它会返回 400 状态代码和错误消息。如果未找到电子邮件,它将调用数据库模块中的创建函数来创建新用户,并返回 201 状态代码和新创建的用户对象。
userRouter.post("/login"):此函数处理对“/login”的 POST 请求以进行用户登录。它从请求正文中提取电子邮件和密码。如果缺少任何这些字段,它将返回 400 状态代码和错误消息。它从数据库模块调用 findByEmail 函数来检查电子邮件是否存在。如果未找到电子邮件,则会返回 404 状态代码和错误消息。如果找到电子邮件,它将调用数据库模块中的comparePassword 函数来检查提供的密码是否与存储的密码匹配。如果密码不匹配,它将返回 400 状态代码和错误消息。如果密码匹配,它将返回 200 状态代码以及用户对象。
userRouter.put('/user/:id'):此函数处理对“/user/:id”的 PUT 请求,其中 :id 代表特定用户的 ID。它从请求正文中提取用户名、电子邮件和密码。如果缺少任何这些字段,它将返回 401 状态代码和错误消息。它从数据库模块调用findOne函数来检查具有指定ID的用户是否存在。如果未找到用户,则会返回 404 状态代码和错误消息。如果找到用户,它会从数据库模块调用更新函数来更新用户的详细信息,并返回 201 状态代码和更新后的用户对象。
userRouter.delete("/user/:id"):此函数处理对“/user/:id”的 DELETE 请求,其中 :id 代表特定用户的 ID。它从请求参数中提取 id。它从数据库模块调用findOne函数来检查具有指定ID的用户是否存在。如果未找到用户,则会返回 404 状态代码和错误消息。如果找到用户,它会调用数据库模块中的删除函数来删除该用户,并返回 200 状态代码和成功消息。
所有这些函数定义了与用户相关的操作的路由和相应的逻辑,例如检索所有用户、检索特定用户、注册新用户、登录用户、更新用户详细信息和删除用户。
最后,为了对这些路由进行 API 调用,我们需要将它们导入到我们的 app.ts 文件中并更新我们的代码,如下所示:
import express from "express"
import * as dotevnv from "dotenv"
import cors from "cors"
import helmet from "helmet"
import { userRouter } from "./users/users.routes"
dotevnv.config()
if (!process.env.PORT) {
console.log(`No port value specified...`)
}
const PORT = parseInt(process.env.PORT as string, 10)
const app = express()
app.use(express.json())
app.use(express.urlencoded({extended : true}))
app.use(cors())
app.use(helmet())
app.use('/', userRouter)
app.listen(PORT, () => {
console.log(`Server is listening on port ${PORT}`)
})
伟大的!现在让我们启动服务器并使用 Postman 测试我们的 API。
npm run dev在你的终端中运行
你的终端应该与此类似
[INFO] 20:55:40 ts-node-dev ver. 2.0.0 (using ts-node ver. 10.9.1, typescript ver. 5.1.3)Server is listening on port 7000
伟大的!让我们调用我们的端点。
注册用户
登录用户
获取所有用户
获取单个用户
更新用户
删除用户:
注意:如果您添加了用户,您的 users.json 文件应不断追加新用户,并且应如下所示。
用户数据存储文件:
最后,让我们为我们的产品创建登录和路由。 因此,让我们将用户界面的内容复制到文件中并进行少量更改./src/product.interface.ts
export interface Product {
name : string,
price : number;
quantity : number;
image : string;
}
export interface UnitProduct extends Product {
id : string
}
export interface Products {
[key : string] : UnitProduct
}
您可以参考有关用户界面的部分,了解有关这些界面的用途的详细信息。
接下来,就像在文件中一样,让我们用类似的逻辑./src/users.database.ts填充 。./src/products.database.ts
import { Product, Products, UnitProduct } from "./product.interface";
import { v4 as random } from "uuid";
import fs from "fs";
let products: Products = loadProducts();
function loadProducts(): Products {
try {
const data = fs.readFileSync("./products.json", "utf-8");
return JSON.parse(data);
} catch (error) {
console.log(`Error ${error}`);
return {};
}
}
function saveProducts() {
try {
fs.writeFileSync("./products.json", JSON.stringify(products), "utf-8");
console.log("Products saved successfully!")
} catch (error) {
console.log("Error", error)
}
}
export const findAll = async () : Promise<UnitProduct[]> => Object.values(products)
export const findOne = async (id : string) : Promise<UnitProduct> => products[id]
export const create = async (productInfo : Product) : Promise<null | UnitProduct> => {
let id = random()
let product = await findOne(id)
while (product) {
id = random ()
await findOne(id)
}
products[id] = {
id : id,
...productInfo
}
saveProducts()
return products[id]
}
export const update = async (id : string, updateValues : Product) : Promise<UnitProduct | null> => {
const product = await findOne(id)
if (!product) {
return null
}
products[id] = {
id,
...updateValues
}
saveProducts()
return products[id]
}
export const remove = async (id : string) : Promise<null | void> => {
const product = await findOne(id)
if (!product) {
return null
}
delete products[id]
saveProducts()
}
同样,您可以参考用户部分,了解有关这些函数为我们的 API 提供的功能的更多详细信息。
一旦我们的逻辑检查通过,就可以为我们的产品实施路线了。
./src/products.routes.ts使用以下代码填充该文件:
import express, {Request, Response} from "express"
import { Product, UnitProduct } from "./product.interface"
import * as database from "./product.database"
import {StatusCodes} from "http-status-codes"
export const productRouter = express.Router()
productRouter.get('/products', async (req : Request, res : Response) => {
try {
const allProducts = await database.findAll()
if (!allProducts) {
return res.status(StatusCodes.NOT_FOUND).json({error : `No products found!`})
}
return res.status(StatusCodes.OK).json({total : allProducts.length, allProducts})
} catch (error) {
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({error})
}
})
productRouter.get("/product/:id", async (req : Request, res : Response) => {
try {
const product = await database.findOne(req.params.id)
if (!product) {
return res.status(StatusCodes.NOT_FOUND).json({error : "Product does not exist"})
}
return res.status(StatusCodes.OK).json({product})
} catch (error) {
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({error})
}
})
productRouter.post("/product", async (req : Request, res : Response) => {
try {
const {name, price, quantity, image} = req.body
if (!name || !price || !quantity || !image) {
return res.status(StatusCodes.BAD_REQUEST).json({error : `Please provide all the required parameters..`})
}
const newProduct = await database.create({...req.body})
return res.status(StatusCodes.CREATED).json({newProduct})
} catch (error) {
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({error})
}
})
productRouter.put("/product/:id", async (req : Request, res : Response) => {
try {
const id = req.params.id
const newProduct = req.body
const findProduct = await database.findOne(id)
if (!findProduct) {
return res.status(StatusCodes.NOT_FOUND).json({error : `Product does not exist..`})
}
const updateProduct = await database.update(id, newProduct)
return res.status(StatusCodes.OK).json({updateProduct})
} catch (error) {
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({error})
}
})
productRouter.delete("/product/:id", async (req : Request, res : Response) => {
try {
const getProduct = await database.findOne(req.params.id)
if (!getProduct) {
return res.status(StatusCodes.NOT_FOUND).json({error : `No product with ID ${req.params.id}`})
}
await database.remove(req.params.id)
return res.status(StatusCodes.OK).json({msg : `Product deleted..`})
} catch (error) {
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({error})
}
})
不要忘记在我们的 app.ts 文件中导入并调用产品的路由,该文件现在应该如下所示:
import express from "express"
import * as dotevnv from "dotenv"
import cors from "cors"
import helmet from "helmet"
import { userRouter } from "./users/users.routes"
import { productRouter } from "./products/product.routes"
dotevnv.config()
if (!process.env.PORT) {
console.log(`No port value specified...`)
}
const PORT = parseInt(process.env.PORT as string, 10)
const app = express()
app.use(express.json())
app.use(express.urlencoded({extended : true}))
app.use(cors())
app.use(helmet())
app.use('/', userRouter)
app.use('/', productRouter)
app.listen(PORT, () => {
console.log(`Server is listening on port ${PORT}`)
})
完美的。我们现在拥有一个使用 Typescript 和 Nodejs 构建的成熟 API。欢呼!!
让我们测试一下我们的端点。
创建产品
所有产品
单品
更新产品
删除产品
如果您添加新产品,它们将被附加到products.json文件中,如下所示:
我们就这样完成了。如果您已经走到这一步,恭喜您并谢谢您!
欢迎提出意见和建议。
你可以在 github 上找到完整的代码 -> GITHUB:/REALSTEVEIG/REST-API-WITH-TYPESCRIPT-NODEJS-AND-A-FILE-BASED-STORAGE-SYSTEM